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

1

5

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.

Ankh answered 22/1, 2020 at 19:32 Comment(1)
I'm having the same exact issue. Probably the RaiseErrorIfClaimsPrincipalAlreadyExists works only with the Write operation. I'm experimenting a bit around it, i'll for sure let you know if i discover anythingCretaceous
C
10

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.

Cretaceous answered 23/1, 2020 at 16:50 Comment(5)
We can check whether the objectId is there, but how to block the journey from moving to the next step?Ankh
That worked, thanks. I haven't think about this "UserMessageIfClaimsTransformationStringsAreNotEqual", Thanks.Ankh
FYI, i edited a bit the answer removing the unnecessary OutputClaimTransformation and renaming things a bit. The logic is the same.Cretaceous
That is help. but i get and error saying - Claim value comparison failed using StringComparison "OrdinalIgnoreCase".Bangup
@Bangup can you share further details about the error?Cretaceous

© 2022 - 2024 — McMap. All rights reserved.