xmlazure-ad-b2cidentity-experience-framework

How to check user exists in AD B2C, using custom policy?


I have a signup flow and it is working fine and it is multi-step:

  1. Contact details
  2. Verification
  3. Password

And now the flow is, after completing all steps a new user will get created, if the user name already exists then in the last step I'm getting an error message that the user already exists. Now I need to change this flow. After entering the contact details(email), I want to check whether this user exists or not. If it exists then I need to show the error message that shows in the last step in the first step itself and block the journey from moving to the next step.

To achieve this, what I did is:

Created a TP that reads the user details using email and put that as a validation technical profile for the first step:

<TechnicalProfile Id="AAD-CheckUserExist">
                    <Metadata>
                        <Item Key="Operation">Read</Item>                        
                        <Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">true</Item>
                    </Metadata>
                    <IncludeInSso>false</IncludeInSso>
                    <InputClaims>                       
                        <InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" />                        
                    </InputClaims>
                    <OutputClaims>
                        <!-- Required claims -->
                        <OutputClaim ClaimTypeReferenceId="objectId" />
                        <OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
                        <!-- Optional claims -->
                        <OutputClaim ClaimTypeReferenceId="userPrincipalName" />
                        <OutputClaim ClaimTypeReferenceId="displayName" />
                        <OutputClaim ClaimTypeReferenceId="accountEnabled" />
                        <OutputClaim ClaimTypeReferenceId="otherMails" />
                        <OutputClaim ClaimTypeReferenceId="signInNames.emailAddress"/>
                        <OutputClaim ClaimTypeReferenceId="signInNames.phoneNumber"/>
                        <OutputClaim ClaimTypeReferenceId="givenName" />
                        <OutputClaim ClaimTypeReferenceId="surname" />
                    </OutputClaims>
                    <IncludeTechnicalProfile ReferenceId="AAD-Common" />
                </TechnicalProfile>

And added <Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">true</Item> to <Metadata>.

Following is the validation profile section:

 <ValidationTechnicalProfiles>
    <ValidationTechnicalProfile ReferenceId="AAD-CheckUserExist" ContinueOnError="false"/>
 </ValidationTechnicalProfiles>

But it is not working as expected, I tried with an existing user after clicking next on the first step it moves the verification step without any error.


Solution

  • The RaiseErrorIfClaimsPrincipalAlreadyExists works only when the Operation is Write.

    After a Read Operation, the objectId claim is populated only if the user already exists. You have to Read with RaiseErrorIfClaimsPrincipalDoesNotExist = false and then you can play with ClaimTransformations and ValidationTechnicalProfiles to block the UserJourney if objectId != null

    EDIT: Example

    I created a AAD-UserReadUsingEmailAddress-RaiseIfExists Technical Profile under AAD Claims Provider

        <TechnicalProfile Id="AAD-UserReadUsingEmailAddress-RaiseIfExists">
          <Metadata>
            <Item Key="Operation">Read</Item>
            <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
          </Metadata>
          <IncludeInSso>false</IncludeInSso>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true" />
          </InputClaims>
          <OutputClaims>
            <!-- Required claims -->
            <OutputClaim ClaimTypeReferenceId="objectId" DefaultValue="NOTFOUND" />
            <OutputClaim ClaimTypeReferenceId="objectIdNotFound" DefaultValue="NOTFOUND" AlwaysUseDefaultValue="true" />
          </OutputClaims>
          <OutputClaimsTransformations>
            <OutputClaimsTransformation ReferenceId="AssertObjectIdObjectIdNotFoundAreEqual" />
          </OutputClaimsTransformations>
          <IncludeTechnicalProfile ReferenceId="AAD-Common" />
        </TechnicalProfile>
    

    As you can see, i'm using the email claim to search for the user and getting only the objectId back. Please note the RaiseErrorIfClaimsPrincipalDoesNotExist = false and the fact that objectId has a defaultValue of NOTFOUND. objectIdNotFound will always be NOTFOUND as per AlwaysUseDefaultValue="true"

    The objectIdNotFound is a simple string claim

      <ClaimType Id="objectIdNotFound">
        <DisplayName>Used for comparison</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
    

    The AssertObjectIdObjectIdNotFoundAreEqual OutputClaim Tranformation is as following:

       <ClaimsTransformation Id="AssertObjectIdObjectIdNotFoundAreEqual" TransformationMethod="AssertStringClaimsAreEqual">
        <InputClaims>
          <InputClaim ClaimTypeReferenceId="objectId" TransformationClaimType="inputClaim1" />
          <InputClaim ClaimTypeReferenceId="objectIdNotFound" TransformationClaimType="inputClaim2" />
        </InputClaims>
        <InputParameters>
          <InputParameter Id="stringComparison" DataType="string" Value="ordinalIgnoreCase" />
        </InputParameters>
      </ClaimsTransformation>
    

    Then i used the AAD-UserReadUsingEmailAddress-RaiseIfExists as a Validation TechnicalProfile in my SelfAsserted TechnicalProfile

        <TechnicalProfile Id="LocalAccountSignUpWithLogonEmail-CheckEmailAlreadyExists">
          <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">Next</Item>
            <Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">There is another user with this email address</Item>
          </Metadata>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="email" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
          </OutputClaims>
          <ValidationTechnicalProfiles>
            <ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress-RaiseIfExists" />
          </ValidationTechnicalProfiles>
        </TechnicalProfile>
    

    With this setup, when you click "Continue", the validation technical profile is executed and it raises an error if there is already an user with the inserted email. This happens because if the user is found, the objectId will be a guid and it won't be equal to "NOTFOUND". You can change the error message via the UserMessageIfClaimsTransformationStringsAreNotEqual metadata.

    HTH, F.