In a B2C custom profile I can't get a custom claim to populate with the user's groups - azure

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>

Related

AADB2C Custom Policy - Local and Social Account Sign policy with split email verification and sign up

I am trying to create an Azure AD B2C custom policy that has the following user journey -
Sign-in / Sign-up with Local Account and Social Accounts wherein the sign-up flow must split the email verification and the actual sign-up page.
To do this, I started with the sample policy - https://github.com/azure-ad-b2c/samples/tree/master/policies/sign-up-with-social-and-local-account
and added the EmailVerification and LocalAccountSignUpWithReadOnlyEmail technical profiles from the sample policy - https://github.com/azure-ad-b2c/samples/tree/master/policies/split-email-verification-and-signup
In order to trigger the split email verification and signup flow, I have set the SignUpTarget to EmailVerification.
I am able to see the sign-in/sign-up page and clicking on the sign up link triggers the email verification flow. However, I am not sure how to get the LocalAccountSignUpWithReadOnlyEmail technical profile triggered after the email verification. Adding this as part of a ClaimsExchange orchestration step causes validation errors while uploading my custom policy.
Here is how my user journey configuration looks like -
<UserJourneys>
<UserJourney Id="SignUpOrSignIn">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="FacebookExchange" />
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
</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="FacebookExchange" TechnicalProfileReferenceId="Facebook-OAUTH" />
<ClaimsExchange Id="EmailVerification" TechnicalProfileReferenceId="EmailVerification" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSignUpWithReadOnlyEmail" TechnicalProfileReferenceId="LocalAccountSignUpWithReadOnlyEmail" />
</ClaimsExchanges>
</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>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="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="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
Here is what the technical profiles look like -
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Email Verification</DisplayName>
<TechnicalProfiles>
<!--Sample: Email verification only-->
<TechnicalProfile Id="EmailVerification">
<DisplayName>Initiate Email Address Verification For Local Account</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.localaccountsignup</Item>
<Item Key="language.button_continue">Continue</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
</OutputClaims>
</TechnicalProfile>
<!-- This technical profile uses a validation technical profile to authenticate the user. -->
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<DisplayName>Local Account Signin</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="SignUpTarget">EmailVerification</Item>
<Item Key="setting.operatingMode">Email</Item>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</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" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<!--Sample: Sign-up self-asserted technical profile without Email verification-->
<TechnicalProfile Id="LocalAccountSignUpWithReadOnlyEmail">
<DisplayName>Email signup</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountsignup</Item>
<Item Key="language.button_continue">Create</Item>
<!-- Sample: Remove sign-up email verification -->
<Item Key="EnforceEmailVerification">False</Item>
</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateReadonlyEmailClaim" />
</InputClaimsTransformations>
<InputClaims>
<!--Sample: Set input the ReadOnlyEmail claim type to prefilled the email address-->
<InputClaim ClaimTypeReferenceId="readOnlyEmail" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<!-- Sample: Display the ReadOnlyEmail claim type (instead of email claim type)-->
<OutputClaim ClaimTypeReferenceId="readOnlyEmail" Required="true" />
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="newUser" />
<!-- Optional claims, to be collected from the user -->
<!--OutputClaim ClaimTypeReferenceId="displayName" /-->
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surName" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
</ValidationTechnicalProfiles>
<!-- Sample: Disable session management for sign-up page -->
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
Here is the error that I get when I tried to upload the policy -
Validation failed: 4 validation error(s) found in policy "B2C_1A_CUSTOM_SIGNUP_SIGNIN" of tenant "testtenant.onmicrosoft.com".User journey "SignUpOrSignIn" in policy "B2C_1A_custom_signup_signin" of tenant "testtenant.onmicrosoft.com" has step 3 with 2 claims exchanges. It must be preceded by a claims provider selection in order to determine which claims exchange can be used.User journey "SignUpOrSignIn" in policy "B2C_1A_custom_signup_signin" of tenant "testtenant.onmicrosoft.com" has step 4 with 2 claims exchanges. It must be preceded by a claims provider selection in order to determine which claims exchange can be used.User journey "SignUpOrSignIn" in policy "B2C_1A_custom_signup_signin" of tenant "testtenant.onmicrosoft.com" has step 5 with 2 claims exchanges. It must be preceded by a claims provider selection in order to determine which claims exchange can be used.User journey "SignUpOrSignIn" in policy "B2C_1A_custom_signup_signin" of tenant "testtenant.onmicrosoft.com" has step 6 with 2 claims exchanges. It must be preceded by a claims provider selection in order to determine which claims exchange can be used.
Looking for some advice here...
The reason why you are getting this error is because you might have written User Journey ID SignUpOrSignIn in 2 files: Base/Extension and Replying Party Policy.
If the count of steps and ClaimsExchange ID is unique, then it will accept or else it will treat as 2 different ClaimsExchange and error will occur while uploading the RP Policy. Please make sure to not duplicate the User Journey, keep only one copy of the User Journey Steps or if you want to extend the Journey steps, then add the steps. For Example: In the Base Policy you have total 5 Steps, then in the Extension or in RP you can start adding new ClaimsExchange from 5th Step and the last step will be JwtIssuer/SamlIssuer.

Azure AD B2C Custom Policy SignUp only with loyaltynumber and email pre-filled

I'm creating a custom Sign up only policy that should receive two attributes in the url:
email adress (optional)
loyaltynumber (required)
If I have the email address in the url I want it to be pre-filled in the box (and read-only, but I could fix that with html/css?) and the loyalty number.
I've allready created the custom policy but I cannot get the email (=username) pre-filled.
I've created a UserJourney:
<UserJourney Id="SignUp">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.idpselections.signup">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="SignUpWithLogonEmailExchange" />
</ClaimsProviderSelections>
</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="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
And a Signup policy:
<BasePolicy>
<TenantId>tenant.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<InputClaims>
<!-- Add the login hint value to the sign-in names claim type -->
<InputClaim ClaimTypeReferenceId="signInName" DefaultValue="{OIDC:LoginHint}" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUp" />
<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}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>

Login screen showing again while accessing profile edit policy after user login in App Azure AD B2C

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

Azure AD B2C Custom Policy with Username Logon

We have created a custom IEF policy in Azure AD B2C to allow for sign-up and sign-in with username identified local accounts, following the instructions found in Custom B2C Policy for Username based Local Accounts answer. When trying to run the SignUpOrSignIn policy from IEF, it displays the following exception on the logon UI.
AADB2C: An exception has occurred.
After enabling Application Insights logging, we captured the following fatal exception:
Orchestration step '1' of in policy 'B2C_1A_signup_signin of tenant 'xxxxxxxxxx.onmicrosoft.com' specifies more than one enabled validation claims exchange
Orchestration Steps configured as follows:
<UserJourney Id="SignUpOrSignIn">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninUsernameExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninUsernameExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Username" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonUsernameExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonName" />
</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="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
Referenced Technical Profile is as follows:
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Username">
<DisplayName>Local Account Signin</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="SignUpTarget">SignUpWithLogonUsernameExchange</Item>
<Item Key="setting.operatingMode">Username</Item>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</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" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
We are unable to find any references to this error on google or SO, and can't figure out where there could be multiple enabled validation claims exchanges? The best we can figure, there is some part of the Base configuration that is not being adequately overridden by the extension configuration, and so it sees duplicates?
You are right the base configuration is not being overridden. We did the following to fix.
In TrustFrameworkExtensions.xml
change the id to something else
<UserJouney id="SignUpOrSignInUsername">
...OrchestrationSteps
</UserJourney>
then navigate in SignUpOrSignIn.xml
change referenceid to the what you put in the extenstions.xml
<DefaultUserJourney ReferenceId="SignUpOrSignInUsername" />

Azure AD B2C Local Account Password stops working after a few hours

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

Resources