We are trying to configure a Custom Policy in AAD B2C that will allow certain users to login via their companies SSO Provider. The Provider is Ping. Using SAML 2.0. Everything is working fine, but after the user successfully logs in to Ping, they are required to be added to the local B2C directory. We don't want these user's in our B2C directory. Is there a way to avoid this?
<ClaimsProvider>
<Domain>Ping</Domain>
<DisplayName>Ping Login</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Ping-SAML">
<DisplayName>Sign in to Ping</DisplayName>
<Description>Login with your Ping account</Description>
<Protocol Name="SAML2"/>
<Metadata>
<Item Key="WantsEncryptedAssertions">false</Item>
<Item Key="WantsSignedAssertions">false</Item>
<Item Key="PartnerEntity">URL FOR FB2C_1A_TrustFrameworkBase</Item>
</Metadata>
<CryptographicKeys>
<Key Id="SamlAssertionSigning" StorageReferenceId="B2C_1A_PingSamlCert"/>
<Key Id="SamlMessageSigning" StorageReferenceId="B2C_1A_PingCert"/>
<Key Id="SamlAssertionDecryption" StorageReferenceId="B2C_1A_PingCert" />
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="uid"/>
<OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid"/>
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="fname"/>
<OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="lname"/>
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="Ping.com" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication"/>
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateDisplayName"/>
<OutputClaimsTransformation ReferenceId="CreateEmail"/>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId"/>
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId"/>
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop"/>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<UserJourney Id="SignUpOrSignInPing">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="PingExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PingExchange" TechnicalProfileReferenceId="Ping-SAML" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
Using a custom policy, you can pass an external identity through, without creating a reference user for it in the Azure AD B2C tenant.
Example:
<UserJourney Id="SignIn">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="PingExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PingExchange" TechnicalProfileReferenceId="PingProfile" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
You might need to generate Azure AD B2C's objectId claim from Ping's issued claims in the PingProfile technical profile, so that Azure AD B2C's JWT issuer can include this objectId claim in a refresh token, if one is issued to your Azure AD B2C application.
Related
I have Azure B2C configured with custom policies to allow signups and sign ins of local accounts and multi-tenant Azure AD. The UI is Angular using MSAL with a .NET Core Web API backend. The custom policy defines a custom claim named clientIds that is populated through a REST call to an internally developed Azure Function.
This custom claim is successfully included in the id_token when the user signs in. However, it is NOT included in the access_token sent to the API. How do I include it in the access token?
Other details:
I found one post that said the solution was to add it as a PersistedClaim to the SM-AAD technical profile, but it didn't have an effect.
If I configure the custom claim in a user flow and an API Connector, everything works great - it's included in both tokens. I've studied the XML of the user flow to look for clues but didn't get anywhere. I cannot use user flows because I must support multi-tenant Azure AD.
I've included the claim in SignUpOrSignin.xml:
<RelyingParty>
<DefaultUserJourney ReferenceId="AegisSignUpOrSignIn" />
<Endpoints>
<Endpoint Id="Token" UserJourneyReferenceId="RedeemRefreshToken" />
</Endpoints>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="identityProvider" />
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
<OutputClaim ClaimTypeReferenceId="otherMails" PartnerClaimType="emails" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="clientIds" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
I think the solution is editing something in TrustFrameworkExtensions.xml but I've been trying all sorts of things for a couple days with no success. Here it is:
<?xml version="1.0" encoding="utf-8" ?>
<TrustFrameworkPolicy
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06"
PolicySchemaVersion="0.3.0.0"
TenantId="aegispremierdev.onmicrosoft.com"
PolicyId="B2C_1A_TrustFrameworkExtensions"
PublicPolicyUri="http://aegispremierdev.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions">
<BasePolicy>
<TenantId>aegispremierdev.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkLocalization</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="prompt">
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="isForgotPassword">
<DisplayName>isForgotPassword</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>Whether the user has selected Forgot your Password</AdminHelpText>
</ClaimType>
<ClaimType Id="clientIds">
<DisplayName>Comma-separated list of authorized CRM clients</DisplayName>
<DataType>string</DataType>
</ClaimType>
</ClaimsSchema>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<Domain>commonaad</Domain>
<DisplayName>Sign in with Microsoft</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AADCommon-OpenIdConnect">
<DisplayName>Sign in with Microsoft</DisplayName>
<Description>Login with your company account</Description>
<Protocol Name="OpenIdConnect"/>
<Metadata>
<Item Key="METADATA">https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration</Item>
<Item Key="client_id">64e56953-cb54-48ee-ae81-3bd1325050d6</Item>
<Item Key="response_types">code</Item>
<Item Key="scope">openid profile email</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="DiscoverMetadataByTokenIssuer">true</Item>
<Item Key="ValidTokenIssuerPrefixes">https://login.microsoftonline.com/</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_AzureAdAppSecret"/>
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="prompt" PartnerClaimType="prompt" DefaultValue= "select_account"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="oid"/>
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="azureAdIdpAuthentication" AlwaysUseDefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="identityProvider" PartnerClaimType="iss" />
<OutputClaim ClaimTypeReferenceId="othermails" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email"/>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<!-- <OutputClaim ClaimTypeReferenceId="clientIds" /> Adding it here doesn't help -->
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId"/>
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId"/>
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-SocialLogin"/>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<Metadata>
<Item Key="client_id">6a178567-c2fb-4f88-93e8-ed5775869485</Item>
<Item Key="IdTokenAudience">3290010d-2d76-4fc0-95fc-6c6ad6c637bb</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="6a178567-c2fb-4f88-93e8-ed5775869485" />
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="3290010d-2d76-4fc0-95fc-6c6ad6c637bb" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" AlwaysUseDefaultValue="true" />
<!-- <OutputClaim ClaimTypeReferenceId="clientIds" /> Adding it here doesn't help -->
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="REST-OnAegisAzureB2CSignUp" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<!-- Adding it as a PersistedClaim as suggested in the referenced SO post doesn't help -->
<!-- <ClaimsProvider>
<DisplayName>Session Management</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SM-AAD">
<DisplayName>Session Mananagement Provider</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.SSO.DefaultSSOSessionProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="signInName" />
<PersistedClaim ClaimTypeReferenceId="authenticationSource" />
<PersistedClaim ClaimTypeReferenceId="identityProvider" />
<PersistedClaim ClaimTypeReferenceId="newUser" />
<PersistedClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" />
<PersistedClaim ClaimTypeReferenceId="clientIds" />
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectIdFromSession" DefaultValue="true" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>-->
<ClaimsProvider>
<DisplayName>Self Asserted</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-Social">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="authenticationSource" />
</InputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="REST-OnAegisAzureB2CSignUp"/>
</ValidationTechnicalProfiles>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>REST APIs - OnAegisAzureB2CSignUp</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="REST-OnAegisAzureB2CSignUp">
<DisplayName>Verify user exists in DonorOne Users table</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<!-- Set the ServiceUrl with your own REST API endpoint -->
<Item Key="ServiceUrl">https://apt-fa-azure-b2c-dev-wus2.azurewebsites.net/api/onAegisAzureB2CSignUp</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AuthenticationType">Basic</Item>
<Item Key="AllowInsecureAuthInProduction">false</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_RestApiUsername" />
<Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_RestApiPassword" />
</CryptographicKeys>
<InputClaims>
<!-- Claims sent to your REST API -->
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="authenticationSource" />
</InputClaims>
<OutputClaims>
<!-- Claims parsed from your REST API -->
<OutputClaim ClaimTypeReferenceId="clientIds" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>REST APIs - OnAegisAzureB2CSignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="REST-OnAegisAzureB2CSignIn">
<DisplayName>Get user extended profile Azure Function web hook</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">https://apt-fa-azure-b2c-dev-wus2.azurewebsites.net/api/onAegisAzureB2CSignIn</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AuthenticationType">Basic</Item>
<Item Key="AllowInsecureAuthInProduction">false</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_RestApiUsername" />
<Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_RestApiPassword" />
</CryptographicKeys>
<InputClaims>
<!-- Claims sent to your REST API -->
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="signInName" />
<InputClaim ClaimTypeReferenceId="authenticationSource" />
</InputClaims>
<OutputClaims>
<!-- Claims parsed from your REST API -->
<OutputClaim ClaimTypeReferenceId="clientIds" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="ForgotPassword">
<DisplayName>Forgot your password?</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="isForgotPassword" DefaultValue="true" AlwaysUseDefaultValue="true"/>
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<Metadata>
<Item Key="setting.forgotPasswordLinkOverride">ForgotPasswordExchange</Item>
</Metadata>
</TechnicalProfile>
<TechnicalProfile Id="LocalAccountWritePasswordUsingObjectId">
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="AegisSignUpOrSignIn">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="AzureADCommonExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="ForgotPasswordExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Check if the user has selected to sign in using one of the social providers -->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
<ClaimsExchange Id="AzureADCommonExchange" TechnicalProfileReferenceId="AADCommon-OpenIdConnect" />
<ClaimsExchange Id="ForgotPasswordExchange" TechnicalProfileReferenceId="ForgotPassword" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="InvokeSubJourney">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>isForgotPassword</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<JourneyList>
<Candidate SubJourneyReferenceId="PasswordReset" />
</JourneyList>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Show self-asserted page only if the directory does not have the user account already (i.e. we do not have an objectId).
This can only happen when authentication happened using a social IDP. If local account was created or authentication done
using ESTS in step 2, then an user account must exist in the directory by this time. -->
<OrchestrationStep Order="5" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Social" TechnicalProfileReferenceId="SelfAsserted-Social" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- This step reads any user attributes that we may not have received when authenticating using ESTS so they can be sent
in the token. -->
<OrchestrationStep Order="6" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>azureAdIdpAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- The previous step (SelfAsserted-Social) could have been skipped if there were no attributes to collect
from the user. So, in that case, create the user in the directory if one does not already exist
(verified using objectId which would be set from the last step if account was created in the directory. -->
<OrchestrationStep Order="7" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="8" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="RESTOnAegisAzureB2CSignIn" TechnicalProfileReferenceId="REST-OnAegisAzureB2CSignIn" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="9" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
<SubJourneys>
<SubJourney Id="PasswordReset" Type="Call">
<OrchestrationSteps>
<!-- Validate user's email address. -->
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PasswordResetUsingEmailAddressExchange" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingEmailAddress" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Collect and persist a new password. -->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
</OrchestrationSteps>
</SubJourney>
</SubJourneys>
</TrustFrameworkPolicy>
#JasSuri-MSFT that was the trick - thx! This was the UserJourney I added to TrustFrameworkExtensions.xml:
<UserJourney Id="AegisRedeemRefreshToken">
<PreserveOriginalAssertion>false</PreserveOriginalAssertion>
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="RefreshTokenSetupExchange" TechnicalProfileReferenceId="RefreshTokenReadAndSetup" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="CheckRefreshTokenDateFromAadExchange" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId-CheckRefreshTokenDate" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- I inserted step 3, which calls the REST API to return the clientIds claim -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="RESTOnAegisAzureB2CSignIn" TechnicalProfileReferenceId="REST-OnAegisAzureB2CSignIn" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
Then I referenced this journey from SignUpOrSignin.xml:
<Endpoint Id="Token" UserJourneyReferenceId="AegisRedeemRefreshToken" />
My REST API expects two claims to be sent in: emailAddress and authenticationSource. They were not included after the above changes, so I attempted to include them by overriding a couple technical profiles. That is, I added this to the ClaimsProviders section in TrustFrameworkExtensions.xml:
<ClaimsProvider>
<DisplayName>Refresh token journey</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="RefreshTokenReadAndSetup">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserReadUsingObjectId-CheckRefreshTokenDate">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
That worked to include the email address, but for reasons I don't yet understand the authenticationSource is still not passed in. But that's out of scope for this question, so I'll end it here.
I've followed the detailed advice here to add a custom policy to my Azure B2C service which is designed to populate a groups claim via an API during the authentication flow.
I've built this on top of a fresh B2C instance and apart from my modifications, the custom policies are those available in the Azure sample here. I'm just using the local accounts sample and my modifications target the SignUpOrSignIn custom policy. For now, all my changes are in the TrustFrameworkBase.xml file.
When I test the policy via the portal with a redirect URI set to https://jwt.ms/ my resultant token does not include a groups claim at all. However, via Application Insights I can see my REST api being called with the correct parameter and according to its logs is successfully loading the user groups and returning the expected result.
Is there anything obvious I'm doing wrong here? This is the user journey I've changed:
<UserJourney Id="SignUpOrSignIn">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- This step reads any user attributes that we may not have received when in the token. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="GetUserGroups" TechnicalProfileReferenceId="GetUserGroups" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
My only change here was to change the order of the SendClaims step to 5 and add a new step 4. This references a GetUserGroups technical profile which I added at the end of the technical profiles under the "Azure Active Directory" claims provider (I wasn't sure if this was correct). It looks like this:
<TechnicalProfile Id="GetUserGroups">
<DisplayName>Retrieves security groups assigned to the user</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">https://<redacted>.azurewebsites.net/api/UserGroups?code=<auth-code></Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">QueryString</Item>
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
<InputClaims>
<InputClaim Required="true" ClaimTypeReferenceId="objectId" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="groups" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
The service URL is an Azure function which accepts a user object Id as objectId query parameters and returns the follow JSON:
{"groups":["b1cc6d36-ac97-420a-8a9a-58a7be4aff36","71fa71e1-7edd-48a7-a147-16705c856cb0"]}
If anyone can point me in the right direction I'd be grateful.
Turns out there was one crucial thing I was missing that wasn't referenced in the guide I was following. I found the answer here. Having retrieved the claim value from my REST API I needed to configure the custom policy to include the claim:
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignIn" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
<OutputClaim ClaimTypeReferenceId="groups" DefaultValue="" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
I'm asking this behalf of one mobile/web developer, i have created some custom policies in AD B2C. Now the developer is trying to integrate this with an application. For some reason i have used:
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:1.2.0
</DataUri>
as the data uri for signin techinical profile. And content defenition is:
<ContentDefinition Id="api.localaccount.login">
<LoadUri>my custom html</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:1.2.0</DataUri>
<Metadata>
<Item Key="DisplayName">Local Account Login</Item>
</Metadata>
<LocalizedResourcesReferences MergeBehavior="Prepend">
<LocalizedResourcesReference Language="en" LocalizedResourcesReferenceId="signin_en" />
<LocalizedResourcesReference Language="es" LocalizedResourcesReferenceId="signin_es" />
</LocalizedResourcesReferences>
</ContentDefinition>
Login TP:
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-CustomUserName-WithoutSignup">
<DisplayName>Local Account Signin UserName</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="setting.operatingMode">Username</Item>
<Item Key="setting.retryLimit">11</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccount.login</Item>
<Item Key="UserMessageIfUserAccountDisabled">You account locked.</Item>
<Item Key="UserMessageIfUserAccountLocked">You've made too many incorrect attempts. Please try again later.</Item>
<Item Key="setting.showCancelButton">false</Item>
<Item Key="language.button_continue">LOG IN</Item>
<Item Key="ServiceThrottled">There are too many requests at this moment. Please wait for some time and try again.</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="password" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="false" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="CheckUserExist" />
<ValidationTechnicalProfile ReferenceId="SendOTP" />
<ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
And my user journey is as follows:
<UserJourneys>
<UserJourney Id="OnBoarding">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.idpselections">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-CustomUserName-WithoutSignup" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="B2CUserProfileUpdateExchange" TechnicalProfileReferenceId="SelfAsserted-AccountValueUpdate" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUserWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="6" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
Policy works fine and when he tries to integrate it with replying party app, after user signup profile update journey called (above user journey), but it is showing the login screen again. How can bypass the login after registration? Is this something that we need to with policy?
Edit:
I'm using separate policies for sign in and sign up. After signin journey works as expected, but after signup the login screen comes.
Look at the starter pack again, you have removed the Session management technical profile reference (SM-AAD) from your sign in page. So during profile edit it won’t get skipped for a signed in user.
https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/blob/master/LocalAccounts/TrustFrameworkBase.xml#L687
The resource owner password credentials flow is now available in preview for Azure B2C:
https://learn.microsoft.com/en-us/azure/active-directory-b2c/configure-ropc
However, I would like to tinker with the claims (specifically: get the username as "email" claim). I tried just calling my existing custom policies in IEF with the flow from the documentation, but they did not like that (unsuprisingly)
AADB2C: An exception has occurred.
Is there a way to influence the claims in this flow?
Update
When implementing Chris' answer I got this error:
Unable to upload policy. Reason : Validation failed: 1 validation error(s) found in policy "B2C_1A_ROPC" of tenant "xxx.onmicrosoft.com".Claim type "email" is the output claim of the relying party's technical profile, but it is not an output claim in any of the steps of user journey "SignIn-ROPC".
I posted an experimental solution as a separate answer.
You have to implement the ROPC flow in a custom policy in order to issue the "email" claim in the ID token.
To implement the ROPC flow in a custom policy:
1: Add the DefaultValue attribute to each of the "signInName" and "password" <InputClaim /> elements in the login-NonInteractive technical profile:
<TechnicalProfile Id="login-NonInteractive">
<DisplayName>Local Account SignIn</DisplayName>
<Protocol Name="OpenIdConnect" />
...
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" Required="true" DefaultValue="{OIDC:Username}" />
<InputClaim ClaimTypeReferenceId="password" Required="true" DefaultValue="{OIDC:Password}" />
...
</InputClaims>
...
</TechnicalProfile>
2: Create a "ROPC" user journey:
<UserJourney Id="SignIn-ROPC">
<PreserveOriginalAssertion>false</PreserveOriginalAssertion>
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LoginNonInteractiveExchange" TechnicalProfileReferenceId="login-NonInteractive" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
3: Create a "ROPC" relying party technical profile:
<RelyingParty>
<DefaultUserJourney ReferenceId="SignIn-ROPC" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="email" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
This is the full policy I got to work using Chris' helpful answer. I would consider it experimental since I don't understand the claims flow fully, but it works pretty well.
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Override some profiles</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<DisplayName>Local Account SignIn</DisplayName>
<Protocol Name="OpenIdConnect" />
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName"
PartnerClaimType="username"
Required="true"
DefaultValue="{OIDC:Username}" />
<InputClaim ClaimTypeReferenceId="password"
Required="true"
DefaultValue="{OIDC:Password}" />
</InputClaims>
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserReadUsingObjectId">
<OutputClaims>
<!-- This user journey does not have any other step that provides this -->
<OutputClaim ClaimTypeReferenceId="signInName" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignIn-ROPC">
<PreserveOriginalAssertion>false</PreserveOriginalAssertion>
<OrchestrationSteps>
<OrchestrationStep Order="1"
Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LoginNonInteractiveExchange"
TechnicalProfileReferenceId="login-NonInteractive" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2"
Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId"
TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3"
Type="SendClaims"
CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
</UserJourneys>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignIn-ROPC" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId"
PartnerClaimType="sub"/>
<!-- This works for accounts that were created via the azure portal -->
<OutputClaim ClaimTypeReferenceId="signInName"
PartnerClaimType="email" />
<!-- This works for accounts that signed up themselves -->
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress"
PartnerClaimType="email" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
I have created custom policies for social and local accounts based on the example from the Active Directory B2C custom policy starter pack for social and local accounts. I have enabled the login with Microsoft and Google and tested that both work, I have also enabled logging in with a local account.
The problem I am seeing is the local account. I can create one and the password works fine for a few hours (not sure exactly how long), then starts giving a generic "Invalid username or password." error. When I type in the wrong password for the same user I get a different message "Your password is incorrect" (this corresponds to a relevant log entry).
I have enabled application insights and can only find the following exceptions.
Any help on how to clear up these 2 errors would be great.
""Statebag"": {
""Complex-CLMS"": {},
""ValidationRequest"": {
""ContentType"": ""Unspecified"",
""Created"": ""2017-10-04T19:17:49.2510644Z"",
""Key"": ""ValidationRequest"",
""Persistent"": true,
""Value"": ""client_id=307&resource=cf87&username=user%domain.com&password=fakep#ss!123&grant_type=password&scope=openid&nca=1;1;login-NonInteractive;False""
},
""ValidationResponse"": {
""ContentType"": ""Json"",
""Created"": ""2017-10-04T19:17:49.2510644Z"",
""Key"": ""ValidationResponse"",
""Persistent"": true,
""Value"": ""{\""error\"":\""invalid_grant\"",\""error_description\"":\""AADSTS65001: The user or administrator has not consented to use the application with ID '307' named 'IdentityExperienceFramework'. Send an interactive authorization request for this user and resource.\\r\\nTrace ID: 7c4\\r\\nCorrelation ID: 3cc\\r\\nTimestamp: 2017-10-04 19:17:49Z\"",\""error_codes\"":[65001],\""timestamp\"":\""2017-10-04 19:17:49Z\"",\""trace_id\"":\""7c4\"",\""correlation_id\"":\""3cc\""};1;login-NonInteractive;False""
},
""ComplexItems"": ""_MachineEventQ, REPRM, TCTX, M_EXCP""
}
Here is the 2nd exception
""Key"": ""Exception"",
""Value"": {
""Kind"": ""Handled"",
""HResult"": ""80131500"",
""Message"": ""The technical Profile with id \""AAD-UserWriteUsingLogonEmail\"" in Policy id \""B2C_1A_signup_signin of Tenant id \""xxx.onmicrosoft.com\"" requires that an error be raised if a claims principal record already exists for storing claims. A claims principal of type \""User\"" with identifier claim type id \""signInNames.emailAddress\"" does already exist."",
""Data"": {
""IsPolicySpecificError"": true,
""TenantId"": ""xxx.onmicrosoft.com"",
""PolicyId"": ""B2C_1A_signup_signin"",
""TechnicalProfile.Id"": ""AAD-UserWriteUsingLogonEmail"",
""ClaimsPrincipal.IdentifierClaim.ClaimTypeId"": ""signInNames.emailAddress"",
""ClaimsPrincipal.PrincipalType"": ""User"",
""CreateClaimsPrincipalIfItDoesNotExist"": ""True"",
""RaiseErrorIfClaimsPrincipalAlreadyExists"": ""True"",
""RaiseErrorIfClaimsPrincipalDoesNotExist"": ""False""
}
}
Here is the content of the TrustFrameworkExtensions.xml file. The only difference between it and the example is that I am using 2 providers instead of 1.
<?xml version="1.0" encoding="utf-8" ?>
<TrustFrameworkPolicy
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06"
PolicySchemaVersion="0.3.0.0"
TenantId="xxx.onmicrosoft.com"
PolicyId="B2C_1A_TrustFrameworkExtensions"
PublicPolicyUri="http://xxx.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions">
<BasePolicy>
<TenantId>xxx.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
</BasePolicy>
<BuildingBlocks>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<Metadata>
<Item Key="client_id">307</Item>
<Item Key="IdTokenAudience">cf8</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="307" />
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="cf8" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<Domain>Employee SignIn with Azure AD</Domain>
<DisplayName>Employee Login</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AzureADProfile">
<DisplayName>Employee Login</DisplayName>
<Description>Login with your GP account</Description>
<Protocol Name="OpenIdConnect"/>
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="METADATA">https://login.windows.net/yyy.onmicrosoft.com/.well-known/openid-configuration</Item>
<Item Key="ProviderName">https://sts.windows.net/7de/</Item>
<Item Key="client_id">f19</Item>
<Item Key="IdTokenAudience">f19</Item>
<Item Key="response_types">id_token</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_AzureADAppSecret"/>
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="oid"/>
<OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid"/>
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="contosoAuthentication" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="AzureADContoso" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId"/>
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId"/>
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop"/>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<Domain>google.com</Domain>
<DisplayName>Google</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Google-OAUTH">
<DisplayName>Employee Login</DisplayName>
<Protocol Name="OAuth2" />
<Metadata>
<Item Key="ProviderName">google</Item>
<Item Key="authorization_endpoint">https://accounts.google.com/o/oauth2/auth</Item>
<Item Key="AccessTokenEndpoint">https://accounts.google.com/o/oauth2/token</Item>
<Item Key="ClaimsEndpoint">https://www.googleapis.com/oauth2/v1/userinfo</Item>
<Item Key="scope">email</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">0</Item>
<Item Key="client_id">zzz.apps.googleusercontent.com</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_GoogleSecret" />
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="id" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="google.com" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-SocialLogin" />
<ErrorHandlers>
<ErrorHandler>
<ErrorResponseFormat>json</ErrorResponseFormat>
<ResponseMatch>$[?(##.error == 'invalid_grant')]</ResponseMatch>
<Action>Reauthenticate</Action>
<!--In case of authorization code used error, we don't want the user to select his account again.-->
<!--AdditionalRequestParameters Key="prompt">select_account</AdditionalRequestParameters-->
</ErrorHandler>
</ErrorHandlers>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignUpOrSignInUsingAzureAD">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="GoogleExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="AzureADExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Check if the user has selected to sign in using one of the social providers -->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="GoogleExchange" TechnicalProfileReferenceId="Google-OAUTH" />
<ClaimsExchange Id="AzureADExchange" TechnicalProfileReferenceId="AzureADProfile" />
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Show self-asserted page only if the directory does not have the user account already (i.e. we do not have an objectId).
This can only happen when authentication happened using a social IDP. If local account was created or authentication done
using ESTS in step 2, then an user account must exist in the directory by this time. -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Social" TechnicalProfileReferenceId="SelfAsserted-Social" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- This step reads any user attributes that we may not have received when authenticating using ESTS so they can be sent
in the token. -->
<OrchestrationStep Order="5" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>socialIdpAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- The previous step (SelfAsserted-Social) could have been skipped if there were no attributes to collect
from the user. So, in that case, create the user in the directory if one does not already exist
(verified using objectId which would be set from the last step if account was created in the directory. -->
<OrchestrationStep Order="6" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="7" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
</TrustFrameworkPolicy>
Since the actual error could be due to a combination of policy and application configuration in the directory, I'll explain why these applications are required to be setup this way, and what could potentially be the issue. I am hoping that even if my guess on the root cause is wrong, this explanation will allow you to debug the issue further.
Think of an application that needs to validate a password for a user. Since the application does not have access to the password, one of the ways is to send an authentication request to login.microsoftonline.com with user's ID and password, and see if a token is successfully issued.
However, when this application sends a request, it must specify some resource, and must also have permissions on user's behalf to access that resource. A common workaround is for the administrator to grant permissions (i.e. consent) on behalf of all users.
IEF uses the same model. Your policy has two applications, one acts as client that gets a token on user's behalf to the resource. If you followed instructions on the "Custom Policies Get Started Page", then ProxyIdentityExperienceFramework is the client, and IdentityExperienceFramework is the resource. As per those instructions, only ProxyIdentityExperienceFramework can get a token to IdentityExperienceFramework on user's behalf and not vice versa.
Based on the error message in your logs, it seems that you may have reversed the IDs for the client and the resource:
The user or administrator has not consented to use the application
with ID '307' named 'IdentityExperienceFramework'
That is, the client called IdentityExperienceFramework does not have access to get a token.
So you will need to check in the Azure AD portal which application has access to which application and update client ID and resource ID.
The second exception may really be by design, it's just telling you that the user that is being created in the directory already exists. This typically happens in sign up and, based on your policy settings, it will typically render an error message to the user indicating that the account already exists and they should instead sign in.
This error
The user or administrator has not consented to use the application with ID '307' named 'IdentityExperienceFramework'.
have you pressed the Grant Permissions button on this app within the AZURE AD blade within Azure
2nd exception
It looks like you are trying to write the user principal again and in your metadata it has the RaiseErrorIfUserPrincipalExists set to rue
The strange thing is neither really helps me to understand why your password doesnt work after a few hours
Best bet is to copy the policy here and we can attempt to tell you why your trying a write instead of a read