Get singin email from Azure AD in a B2C custom policy

Patrice Côté 21 Reputation points
2022-03-07T16:47:52.847+00:00

In AAD B2C I have users created with 2 identities. I used graph to create them with this body :

   lang-json  
   {  
     "displayName": "John Doe",  
     "mail":"******@gmail.com",  
     "identities": [  
       {  
         "signInType": "userName",  
         "issuer": "mytenant.onmicrosoft.com",  
         "issuerAssignedId": "606198"  
       },  
       {  
         "signInType": "emailAddress",  
         "issuer": "mytenant.onmicrosoft.com",  
         "issuerAssignedId": "******@gmail.com"  
       }  
     ],  
     "passwordProfile" : {  
       "password": "Soleil!23",  
       "forceChangePasswordNextSignIn": false  
     },  
     "passwordPolicies": "DisablePasswordExpiration"  
   }  

This allow the user to connect either with an email (johndoe19287456@Stuff .com) or an ID (606198).

When a user input his ID and then click on the "Forgot password?" link, I'd like to get the email value from AAD so the user cannot input whatever he wants. But I'd still like it to be "verified" by sending a code to that email address. I have 2 problems :

  • I can't fnd a way to get the email value from AzureActiveDirectoryProvider
  • I can't find a way to populate the Verified.Email field (and make it readonly).

Here's a sample of one of the many things I've tried yet.

Building blocks custom claims :

   lang-xml  
         <ClaimType Id="ReadOnlyEmail">  
           <DisplayName>Verified Email Address</DisplayName>  
           <DataType>string</DataType>  
           <UserInputType>Readonly</UserInputType>  
         </ClaimType>  
         <ClaimType Id="emailFromAAD">  
           <DisplayName>Email from AAD</DisplayName>  
           <DataType>string</DataType>  
           <UserHelpText />  
           <UserInputType>Readonly</UserInputType>  
         </ClaimType>  
         <ClaimType Id="readOnlySignInName">  
           <DisplayName>Sign in name</DisplayName>  
           <DataType>string</DataType>  
           <UserHelpText />  
           <UserInputType>Readonly</UserInputType>  
         </ClaimType>  
         <ClaimType Id="emailValue">  
           <DisplayName>Matched mail</DisplayName>  
           <DataType>string</DataType>  
         </ClaimType>  
         <ClaimType Id="isEmailBoolean">  
           <DisplayName>is Email</DisplayName>  
           <DataType>boolean</DataType>  
         </ClaimType>  
         <ClaimType Id="strongAuthenticationEmailAddress">  
           <DisplayName>string</DisplayName>  
           <DataType>string</DataType>  
           <AdminHelpText>Email address that the user can use for strong authentication.</AdminHelpText>  
           <UserHelpText>Email address to use for strong authentication.</UserHelpText>  
           <UserInputType>TextBox</UserInputType>  
         </ClaimType>  

Claims transformation :

   lang-xml  
   <ClaimsTransformation Id="CopySignInNameFromReadOnly" TransformationMethod="FormatStringClaim">  
           <InputClaims>  
             <InputClaim ClaimTypeReferenceId="readOnlySignInName" TransformationClaimType="inputClaim" />  
           </InputClaims>  
           <InputParameters>  
             <InputParameter Id="stringFormat" DataType="string" Value="{0}" />  
           </InputParameters>  
           <OutputClaims>  
             <OutputClaim ClaimTypeReferenceId="signInName" TransformationClaimType="outputClaim" />  
           </OutputClaims>  
         </ClaimsTransformation>  
     
         <!-- If signin name match the regex, it is an email identifier. Oterwise, it'll be considered as username -->  
         <ClaimsTransformation Id="isEmail" TransformationMethod="setClaimsIfRegexMatch">  
           <InputClaims>  
             <InputClaim ClaimTypeReferenceId="readOnlySignInName" TransformationClaimType="claimToMatch" />  
           </InputClaims>  
           <InputParameters>  
             <InputParameter Id="matchTo" DataType="string" Value="[^@]+@[^\.]+\..+" />  
             <InputParameter Id="outputClaimIfMatched" DataType="string" Value="isEmail" />  
           </InputParameters>  
           <OutputClaims>  
             <OutputClaim ClaimTypeReferenceId="emailValue" TransformationClaimType="outputClaim" />  
             <OutputClaim ClaimTypeReferenceId="isEmailBoolean" TransformationClaimType="regexCompareResultClaim" />  
           </OutputClaims>  
         </ClaimsTransformation>  

Technical profiles:

   lang-xml  
   <!-- Password reset step 1b - Included in step SelfAsserted-LocalAccountLookup-Combined-PwdReset   
           That's where the input claim is define. signInName = the Username field on the screen -->  
           <TechnicalProfile Id="SelfAsserted-LocalAccountLookup-Combined-SignUp">  
             <DisplayName>Local Account Sign Up</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.selfasserted</Item>  
               <Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>  
             </Metadata>  
             <IncludeInSso>false</IncludeInSso>  
             <InputClaims>  
               <InputClaim ClaimTypeReferenceId="readOnlySignInName" DefaultValue="{OIDC:LoginHint}" AlwaysUseDefaultValue="true" />  
             </InputClaims>  
             <OutputClaims>  
             <OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />  
               <OutputClaim ClaimTypeReferenceId="isEmailBoolean" />  
             </OutputClaims>  
             <OutputClaimsTransformations>  
               <OutputClaimsTransformation ReferenceId="CopySignInNameFromReadOnly" />  
             </OutputClaimsTransformations>  
             <ValidationTechnicalProfiles>  
               <ValidationTechnicalProfile ReferenceId="regexAnalysisUsername" />  
             </ValidationTechnicalProfiles>  
             <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />  
           </TechnicalProfile>  
     
   <!-- Password reset step 1a. Includes SelfAsserted-LocalAccountLookup-Combined-SignUp   
           Input claim is defined in this. Here, we define output claims -->  
           <TechnicalProfile Id="SelfAsserted-LocalAccountLookup-Combined-PwdReset">  
             <DisplayName>Reset password</DisplayName>  
             <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />  
             <Metadata>  
               <Item Key="setting.showCancelButton">false</Item>  
               <Item Key="IpAddressClaimReferenceId">IpAddress</Item>  
               <Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>  
               <Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>  
             </Metadata>  
             <OutputClaims>  
               <OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />  
               <OutputClaim ClaimTypeReferenceId="isEmailBoolean" />  
               <OutputClaim ClaimTypeReferenceId="objectId" />  
               <OutputClaim ClaimTypeReferenceId="emailFromAAD" />  
             </OutputClaims>  
             <ValidationTechnicalProfiles>  
               <ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingIdentifier" />  
               <ValidationTechnicalProfile ReferenceId="regexAnalysisUsername" />  
             </ValidationTechnicalProfiles>  
             <IncludeTechnicalProfile ReferenceId="SelfAsserted-LocalAccountLookup-Combined-SignUp" />  
             <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />  
           </TechnicalProfile>  
     
           <!-- Password reset step 1c. Verify signin name on the "Continue" button clicked in the first screen -->  
           <TechnicalProfile Id="AAD-UserReadUsingIdentifier">  
             <Metadata>  
               <Item Key="Operation">Read</Item>  
               <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>  
             </Metadata>  
             <IncludeInSso>false</IncludeInSso>  
             <InputClaims>  
               <InputClaim ClaimTypeReferenceId="readOnlySignInName" PartnerClaimType="signInNames" Required="true" />  
             </InputClaims>  
             <OutputClaims>  
               <OutputClaim ClaimTypeReferenceId="objectId" />  
               <OutputClaim ClaimTypeReferenceId="emailFromAAD" PartnerClaimType="signInNames.emailAddress" />  
               <OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />  
             </OutputClaims>  
             <IncludeTechnicalProfile ReferenceId="AAD-Common" />  
           </TechnicalProfile>  
     
   <!-- Passwod reset step 2. Only if sign in data was a username -->  
           <TechnicalProfile Id="LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress">  
             <DisplayName>Reset password using username</DisplayName>  
             <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />  
             <Metadata>  
               <!-- Other values maybe defined in localized api.selfasserted.fr and api.selfasserted.en -->  
               <Item Key="IpAddressClaimReferenceId">IpAddress</Item>  
               <Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>  
               <Item Key="AllowGenerationOfClaimsWithNullValues">true</Item>  
               <Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">An account could not be found for the provided User ID and email combination.</Item>  
               <Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>  
               <Item Key="LocalAccountType">Username</Item>  
               <Item Key="LocalAccountProfile">true</Item>  
               <!-- Reduce the default self-asserted retry limit of 7 for the reset journey -->  
               <Item Key="setting.retryLimit">5</Item>  
             </Metadata>  
             <CryptographicKeys>  
               <Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />  
             </CryptographicKeys>  
             <InputClaims>  
               <InputClaim ClaimTypeReferenceId="readOnlySignInName" />  
             </InputClaims>  
             <OutputClaims>  
               <OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />  
               <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" DefaultValue="strongAuthenticationEmailAddress" Required="true" />  
               <OutputClaim ClaimTypeReferenceId="authenticationSource" />  
               <OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />  
               <OutputClaim ClaimTypeReferenceId="emailFromAAD" />  
             </OutputClaims>  
             <ValidationTechnicalProfiles>  
               <ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingUserNameAndValidateStrongAuthenticationEmailAddress" />  
             </ValidationTechnicalProfiles>  
             <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />  
           </TechnicalProfile>  

For now, I get the first screen with the readonly ID working.

180648-2022-03-03-16-23-56-2.png

The next screen presents the textboxes for email. The Email from AAD shouldn't visible.

180761-2022-03-03-16-24-21-2.png

But anyway it is empty, showing that it didn't get anything from AAD or I failed to properly store it in the claim bag or pass it down. Note that I have tried getting the value both from "strongAuthenticationEmailAddress" and "signInNames.emailAddress" based on Microsoft documentation but none of it works. Maybe it's in the way I define my output claim wit PartnerClaimType in the AAD-UserReadUsingIdentifier profile?

To make it clear for everyone, here's what I'd like to have. A simple page with 2 reaonly fields with a button to send the code and then another one to continue after the code has been verified.

180762-wanted-result.png

Can anyone help me with this one?

I started from the B2C custom policies starter pack and added customization from this community repo.

Microsoft Security | Microsoft Entra | Microsoft Entra External ID
{count} votes

1 answer

Sort by: Most helpful
  1. Patrice Côté 21 Reputation points
    2022-03-09T15:21:55.83+00:00

    Here's the answer I posted on StackOverflow. I publish it here too so you want have to search for it.

    I managed to get email address from AAD by following Jas Suri - MSFT advice with a few other things. So just to sum things up here's what I did. I modified the Validation TP
    AAD-UserReadUsingIdentifier
    by replacing the output claim
    emailFromAAD
    simply by
    signInNames.emailAddress
    (no more PartnerClaimType). In the
    LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress
    TP, I added the InputClaim
    emailFromAAD
    that I populate with a CopyClaim input claims transformation. I also cleaned up the first TP of the journey
    SelfAsserted-LocalAccountLookup-Combined-PwdReset
    :

    lang-xml
    <ClaimsTransformation Id="CopySignInEmailAddressToEmail" TransformationMethod="CopyClaim">
        <InputClaims>
          <InputClaim ClaimTypeReferenceId="signInNames.emailAddress" TransformationClaimType="inputClaim" />
        </InputClaims>
        <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="emailFromAAD" TransformationClaimType="outputClaim" />
        </OutputClaims>
    </ClaimsTransformation>
    
    <TechnicalProfile Id="SelfAsserted-LocalAccountLookup-Combined-PwdReset">
              <DisplayName>Reset password</DisplayName>
              <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
              <Metadata>
                <Item Key="setting.showCancelButton">false</Item>
                <Item Key="IpAddressClaimReferenceId">IpAddress</Item>
                <Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
                <Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
              </Metadata>
              <OutputClaims>
                <OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
                <OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
                <OutputClaim ClaimTypeReferenceId="objectId" />
                <OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
                <OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
              </OutputClaims>
              <ValidationTechnicalProfiles>
                <ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingIdentifier" />
                <ValidationTechnicalProfile ReferenceId="regexAnalysisReadOnlyUsername" />
              </ValidationTechnicalProfiles>
              <IncludeTechnicalProfile ReferenceId="SelfAsserted-LocalAccountLookup-Combined-SignUp" />
              <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
            </TechnicalProfile>
    
    <TechnicalProfile Id="AAD-UserReadUsingIdentifier">
              <Metadata>
                <Item Key="Operation">Read</Item>
                <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
              </Metadata>
              <IncludeInSso>false</IncludeInSso>
              <InputClaims>
                <InputClaim ClaimTypeReferenceId="readOnlySignInName" PartnerClaimType="signInNames" Required="true" />
              </InputClaims>
              <OutputClaims>
                <OutputClaim ClaimTypeReferenceId="objectId" />
                <OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
                <OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
              </OutputClaims>
              <IncludeTechnicalProfile ReferenceId="AAD-Common" />
            </TechnicalProfile>
    
    <TechnicalProfile Id="LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress">
          <DisplayName>Reset password using username</DisplayName>
          <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          <Metadata>
            <!-- Other values maybe defined in localized api.selfasserted.fr and api.selfasserted.en -->
            <Item Key="IpAddressClaimReferenceId">IpAddress</Item>
            <Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
            <Item Key="AllowGenerationOfClaimsWithNullValues">true</Item>
            <Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">An account could not be found for the provided User ID and email combination.</Item>
            <Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
            <Item Key="LocalAccountType">Username</Item>
            <Item Key="LocalAccountProfile">true</Item>
            <!-- Reduce the default self-asserted retry limit of 7 for the reset journey -->
            <Item Key="setting.retryLimit">5</Item>
          </Metadata>
          <CryptographicKeys>
            <Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
          </CryptographicKeys>
          <InputClaimsTransformations>
            <InputClaimsTransformation ReferenceId="CopySignInEmailAddressToEmail" />
          </InputClaimsTransformations>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="readOnlySignInName" />
            <InputClaim ClaimTypeReferenceId="emailFromAAD" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
            <OutputClaim ClaimTypeReferenceId="emailFromAAD" PartnerClaimType="Verified.Email" Required="true" />
            <OutputClaim ClaimTypeReferenceId="authenticationSource" />
          </OutputClaims>
          <ValidationTechnicalProfiles>
            <ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingUserNameAndValidateStrongAuthenticationEmailAddress" />
          </ValidationTechnicalProfiles>
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
        </TechnicalProfile>
    
    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.