Adding login hint for split email verification password custom policy

DK 0 Reputation points
2024-10-22T10:58:42.6133333+00:00

I have a custom policy that is based on the sample provided here https://github.com/azure-ad-b2c/samples/tree/master/policies/split-email-verification-and-signup to have the email verification and password split between two separate screens.

I've been trying to add the the login_hint to have the email field pre-filled but there is some strange behavior. I tried to add the following:

<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>

  <InputClaims>
     <InputClaim ClaimTypeReferenceId="email" DefaultValue="{OIDC:LoginHint}" AlwaysUseDefaultValue="true" />
   </InputClaims>

In the email verification below

<ClaimsProvider>
         <DisplayName>Email Verification</DisplayName>
         <TechnicalProfiles>
            <!--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.localaccount.emailVerification</Item>
                  <Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
                  <Item Key="EnforceEmailVerification">True</Item>
                  <Item Key="language.button_continue">Continue</Item>
               </Metadata>
               <InputClaims>
                  <InputClaim ClaimTypeReferenceId="email" DefaultValue="{OIDC:LoginHint}" AlwaysUseDefaultValue="true" />
               </InputClaims>
               <OutputClaims>
                  <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
               </OutputClaims>
            </TechnicalProfile>
         </TechnicalProfiles>
      </ClaimsProvider>

It seems to work, however when it reads the email from the login hint, the verification button somehow disappears, and the user can proceed without verifying his email. If it does not detect a login hint and the user actually has to enter his email, then the verification button is there.

Below is the complete policy ( without the changes above ). If anyone has any idea what the issue is, I would be happy to hear about it. Thanks

<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="contactifybiztest.onmicrosoft.com" PolicyId="B2C_1A_signup_only" PublicPolicyUri="http://contactifybiztest.onmicrosoft.com/B2C_1A_signup_only" TenantObjectId="76293fdb-8269-4ba9-a113-d99adce461c3">
   <BasePolicy>
      <TenantId>contactifybiztest.onmicrosoft.com</TenantId>
      <PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
   </BasePolicy>
   <BuildingBlocks>
      <ClaimsSchema>
         <!-- Read only email address to present to the user-->
         <ClaimType Id="readonlyEmail">
            <DisplayName>E-mail Address</DisplayName>
            <DataType>string</DataType>
            <UserInputType>Readonly</UserInputType>
         </ClaimType>
      </ClaimsSchema>
      <ClaimsTransformations>
         <ClaimsTransformation Id="CreateReadonlyEmailClaim" TransformationMethod="FormatStringClaim">
            <InputClaims>
               <InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim" />
            </InputClaims>
            <InputParameters>
               <InputParameter Id="stringFormat" DataType="string" Value="{0}" />
            </InputParameters>
            <OutputClaims>
               <OutputClaim ClaimTypeReferenceId="readonlyEmail" TransformationClaimType="outputClaim" />
            </OutputClaims>
         </ClaimsTransformation>
      </ClaimsTransformations>
      <ContentDefinitions>
         <ContentDefinition Id="api.localaccount.emailVerification">
            <LoadUri>~/tenant/templates/AzureBlue/selfAsserted.cshtml</LoadUri>
            <RecoveryUri>~/common/default_page_error.html</RecoveryUri>
            <DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.8</DataUri>
            <Metadata>
               <Item Key="DisplayName">Collect information from user page</Item>
            </Metadata>
            <LocalizedResourcesReferences MergeBehavior="Prepend">
               <LocalizedResourcesReference Language="en" LocalizedResourcesReferenceId="api.localaccount.emailVerification.en" />
            </LocalizedResourcesReferences>
         </ContentDefinition>
      </ContentDefinitions>
      <Localization Enabled="true">
         <SupportedLanguages DefaultLanguage="en" MergeBehavior="ReplaceAll">
            <SupportedLanguage>en</SupportedLanguage>
         </SupportedLanguages>
         <LocalizedResources Id="api.localaccount.emailVerification.en">
            <LocalizedStrings>
               <LocalizedString ElementType="UxElement" StringId="button_continue">Continue</LocalizedString>
            </LocalizedStrings>
         </LocalizedResources>
      </Localization>
   </BuildingBlocks>
   <ClaimsProviders>
      <ClaimsProvider>
         <DisplayName>Email Verification</DisplayName>
         <TechnicalProfiles>
            <!--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.localaccount.emailVerification</Item>
                  <Item Key="language.button_continue">Continue</Item>
               </Metadata>
               <OutputClaims>
                  <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
               </OutputClaims>
            </TechnicalProfile>
         </TechnicalProfiles>
      </ClaimsProvider>
      <ClaimsProvider>
         <DisplayName>Local Account</DisplayName>
         <TechnicalProfiles>
            <!--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>
                  <!-- 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="executed-SelfAsserted-Input" DefaultValue="true" />
                  <OutputClaim ClaimTypeReferenceId="authenticationSource" />
                  <OutputClaim ClaimTypeReferenceId="newUser" />
                  <!-- Optional claims, to be collected from the user -->
               </OutputClaims>
               <ValidationTechnicalProfiles>
                  <ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
               </ValidationTechnicalProfiles>
               <!-- Sample: Disable session management for sign-up page -->
               <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
            </TechnicalProfile>
         </TechnicalProfiles>
      </ClaimsProvider>
   </ClaimsProviders>
   <UserJourneys>
      <UserJourney Id="SignUp">
         <OrchestrationSteps>
            <!-- Start with email verification -->
            <OrchestrationStep Order="1" Type="ClaimsExchange">
               <ClaimsExchanges>
                  <ClaimsExchange Id="SignUpWithLogonEmailExchange_EmailVerification" TechnicalProfileReferenceId="EmailVerification" />
               </ClaimsExchanges>
            </OrchestrationStep>
            <!-- Proceed to the sign-up page -->
            <OrchestrationStep Order="2" Type="ClaimsExchange">
               <ClaimsExchanges>
                  <ClaimsExchange Id="SignUpWithLogonEmailExchange_WithReadOnlyEmail" TechnicalProfileReferenceId="LocalAccountSignUpWithReadOnlyEmail" />
               </ClaimsExchanges>
            </OrchestrationStep>
            <!-- Read the user after sign-up -->
            <OrchestrationStep Order="3" Type="ClaimsExchange">
               <ClaimsExchanges>
                  <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
               </ClaimsExchanges>
            </OrchestrationStep>
            <!-- Issue the token -->
            <OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
         </OrchestrationSteps>
      </UserJourney>
   </UserJourneys>
   <RelyingParty>
      <DefaultUserJourney ReferenceId="SignUp" />
      <TechnicalProfile Id="PolicyProfile">
         <DisplayName>PolicyProfile</DisplayName>
         <Protocol Name="OpenIdConnect" />
         <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="email" />
            <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
            <OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="" />
            <OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
         </OutputClaims>
         <SubjectNamingInfo ClaimType="sub" />
      </TechnicalProfile>
   </RelyingParty>
</TrustFrameworkPolicy>
Microsoft Entra ID
Microsoft Entra ID
A Microsoft Entra identity service that provides identity management and access control capabilities. Replaces Azure Active Directory.
22,064 questions
{count} votes

1 answer

Sort by: Most helpful
  1. James Hamil 24,921 Reputation points Microsoft Employee
    2024-10-24T18:02:28.2533333+00:00

    Hi @DK , the issue you're encountering is likely due to the way Azure AD B2C handles the EnforceEmailVerification and the pre-filled email from the login_hint. When the email is pre-filled, the system might be considering it as already verified, hence the verification button disappears.

    You need to make sure that the email is verified even if it is pre-filled. One way to handle this is by using a claim transformation to ensure the email verification step is properly enforced.

    Here are the steps to modify your policy:

    1. Update the EmailVerification Technical Profile:
      • Add a claim transformation to ensure the email is always treated as unverified initially.
      • Check the EnforceEmailVerification metadata item is set properly.
    <ClaimsProvider>
        <DisplayName>Email Verification</DisplayName>
        <TechnicalProfiles>
            <!-- 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.localaccount.emailVerification</Item>
                    <Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
                    <Item Key="EnforceEmailVerification">True</Item>
                    <Item Key="language.button_continue">Continue</Item>
                </Metadata>
                <InputClaims>
                    <InputClaim ClaimTypeReferenceId="email" DefaultValue="{OIDC:LoginHint}" AlwaysUseDefaultValue="true" />
                </InputClaims>
                <OutputClaims>
                    <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
                    <OutputClaim ClaimTypeReferenceId="isEmailVerified" DefaultValue="false" />
                </OutputClaims>
                <OutputClaimsTransformations>
                    <OutputClaimsTransformation ReferenceId="MarkEmailAsUnverified" />
                </OutputClaimsTransformations>
            </TechnicalProfile>
        </TechnicalProfiles>
    </ClaimsProvider>
    
    1. Define the MarkEmailAsUnverified Claims Transformation:
      • This transformation will ensure the isEmailVerified claim is set to false initially.
    <ClaimsTransformations>
        <ClaimsTransformation Id="MarkEmailAsUnverified" TransformationMethod="AssertBooleanClaimIsEqualToValue">
            <InputClaims>
                <InputClaim ClaimTypeReferenceId="isEmailVerified" TransformationClaimType="inputClaim" />
            </InputClaims>
            <InputParameters>
                <InputParameter Id="valueToAssert" DataType="boolean" Value="false" />
            </InputParameters>
            <OutputClaims>
                <OutputClaim ClaimTypeReferenceId="isEmailVerified" TransformationClaimType="outputClaim" />
            </OutputClaims>
        </ClaimsTransformation>
    </ClaimsTransformations>
    
    1. Ensure EnforceEmailVerification is set to True in the EmailVerification Technical Profile.
    2. Modify the User Journey:
      • Verify that the email verification step is always executed and that the isEmailVerified claim is checked.
    <UserJourney Id="SignUp">
        <OrchestrationSteps>
            <!-- Start with email verification -->
            <OrchestrationStep Order="1" Type="ClaimsExchange">
                <Preconditions>
                    <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
                        <Value>isEmailVerified</Value>
                        <Value>True</Value>
                        <Action>SkipThisOrchestrationStep</Action>
                    </Precondition>
                </Preconditions>
                <ClaimsExchanges>
                    <ClaimsExchange Id="SignUpWithLogonEmailExchange_EmailVerification" TechnicalProfileReferenceId="EmailVerification" />
                </ClaimsExchanges>
            </OrchestrationStep>
            <!-- Proceed to the sign-up page -->
            <OrchestrationStep Order="2" Type="ClaimsExchange">
                <ClaimsExchanges>
                    <ClaimsExchange Id="SignUpWithLogonEmailExchange_WithReadOnlyEmail" TechnicalProfileReferenceId="LocalAccountSignUpWithReadOnlyEmail" />
                </ClaimsExchanges>
            </OrchestrationStep>
            <!-- Read the user after sign-up -->
            <OrchestrationStep Order="3" Type="ClaimsExchange">
                <ClaimsExchanges>
                    <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
                </ClaimsExchanges>
            </OrchestrationStep>
            <!-- Issue the token -->
            <OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
        </OrchestrationSteps>
    </UserJourney>
    

    Please let me know if you have any questions and I can help you further.

    If this answer helps you please mark "Accept Answer" so other users can reference it.

    Thank you,

    James

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.