0
votes

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

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

This allow the user to connect either with an email (johndoe19287456@gmail.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 :

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

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

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

enter image description here

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

enter image description here

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

enter image description here

Can anyone help me with this one?

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

UPDATE Here is what I get from MS Graph when querying for my user :

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(identities,id,displayName,mail,surname,userPrincipalName,extension_06a19ccd80a0430c9730c62e4d96c895_ClientID,extension_06a19ccd80a0430c9730c62e4d96c895_requiresMigration)/$entity",
    "id": "051e***************",
    "displayName": "test - Tests2",
    "mail": "johndoe19287456@gmail.com",
    "surname": null,
    "userPrincipalName": "051ea****************@mytenant.onmicrosoft.com",
    "identities": [
        {
            "signInType": "emailAddress",
            "issuer": "mytenant.onmicrosoft.com",
            "issuerAssignedId": "johndoe19287456@gmail.com"
        },
        {
            "signInType": "userName",
            "issuer": "mytenant.onmicrosoft.com",
            "issuerAssignedId": "606198"
        },
        {
            "signInType": "userPrincipalName",
            "issuer": "mytenant.onmicrosoft.com",
            "issuerAssignedId": "051ea****************@mytenant.onmicrosoft.com"
        }
    ]
}

2

2 Answers

0
votes

You must provide the emailFromAAD as an input claim in LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress for it to be pre populated here.

https://docs.microsoft.com/en-us/azure/active-directory-b2c/self-asserted-technical-profile#input-claims

          <InputClaims>
            <InputClaim ClaimTypeReferenceId="readOnlySignInName" PartnerClaimType="signInNames" Required="true" />
            <InputClaim ClaimTypeReferenceId="emailFromAAD" />
          </InputClaims>

Now it will be prepopulated on this page.

If you want to force verification of this email, copy it into a read only claim using a claim transform, then do:

<OutputClaim ClaimTypeReferenceId="readOnlyEmailFromAAD" PartnerClaimType="Verified.Email" Required="true" />

0
votes

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 :

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