0
votes

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...

1

1 Answers

1
votes

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.