azure-ad-b2cazure-ad-b2c-custom-policyidentity-experience-framework

ADB2C Custom Policy to attempt login-NonInteractive then attempt login via REST API


I'm trying to create a custom login policy within Azure ADB2C (Identity Experience Framework), where we capture email address and password, then use these claims to attempt local account sign in (login-NonInteractive), but (and this is the awkward part) if the email/password is not valid against a local account, then call a REST API to see if this matches a legacy account on our external system.

If it is not valid against a legacy account either, then we display the 'email/password invalid' message (otherwise we can create a local account). This is basically an attempt at 'seamless migration'.

I have a very crude understanding of Identity Experience Framework, so my approach may be wrong (or just simply not possible?). My attempt so far is to have a user journey orchestration step that looks like

<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
  <ClaimsProviderSelections>
      <ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
  </ClaimsProviderSelections>
  <ClaimsExchanges>
    <ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email-ThenApiSignin" />
  </ClaimsExchanges>
</OrchestrationStep>

The technical profile it's using there is a slight tweak to the standard SelfAsserted-LocalAccountSignin-Email that looks like this:

<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email-ThenApiSignin">
  <DisplayName>Local Account Signin followed by Api Signin</DisplayName>
  <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  <Metadata>
    <Item Key="SignUpTarget">SignUpWithLogonEmailExchange</Item>
    <Item Key="setting.operatingMode">Email</Item>
    <Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
  </Metadata>
  <IncludeInSso>false</IncludeInSso>
  <InputClaims>
    <InputClaim ClaimTypeReferenceId="signInName" />
  </InputClaims>
  <OutputClaims>
    <OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
    <OutputClaim ClaimTypeReferenceId="password" Required="true" />
    <OutputClaim ClaimTypeReferenceId="objectId" />
    <OutputClaim ClaimTypeReferenceId="authenticationSource" />
  </OutputClaims>
  <ValidationTechnicalProfiles>
    <ValidationTechnicalProfile ReferenceId="login-NonInteractive" ContinueOnError="true"/>
    <ValidationTechnicalProfile ReferenceId="UserRegistrationServiceApiLogin" />
  </ValidationTechnicalProfiles>
  <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>

..the important bit (I thought) was the ContinueOnError from any issues with (standard, unchanged) login-NonInteractive profile. My hope was that if the login failed it would call a second technical profile I have there to attempt login via an external rest api UserRegistrationServiceApiLogin, but I don't believe this is being hit.

Is it possible to do what I'm trying? Namely to make one attempt at login-NonInteractive followed by a different validation profile that can attempt login a different way?


Solution

  • I had a similar situation. I first checked if the user exists and based on the claim I completed other steps.

    <ValidationTechnicalProfiles>
    <ValidationTechnicalProfile ReferenceId="AAD-UserExists" ContinueOnError="true" ContinueOnSuccess="true" />
    <!-- If 'isLocalUser' equals 'True' Login Locally -->
    <ValidationTechnicalProfile ReferenceId="login-NonInteractive">
        <Preconditions>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
                <Value>isLocalUser</Value>
                <Action>SkipThisValidationTechnicalProfile</Action>
            </Precondition>
        </Preconditions>
    </ValidationTechnicalProfile>
    <!-- If 'isLocalUser' equals 'False' Login Remotely -->
    <ValidationTechnicalProfile ReferenceId="UserRegistrationServiceApiLogin">
        <Preconditions>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
                <Value>isLocalUser</Value>
                <Action>SkipThisValidationTechnicalProfile</Action>
            </Precondition>
        </Preconditions>
    </ValidationTechnicalProfile>   
    

    You can use the following technical profile then:

    <TechnicalProfile Id="AAD-UserExists">
                    <Metadata>
                        <Item Key="Operation">Read</Item>
                        <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
                    </Metadata>
                    <IncludeInSso>false</IncludeInSso>
                    <InputClaims>
                        <InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="signInNames.emailAddress" Required="true" />
                    </InputClaims>
                    <OutputClaims>
                        <OutputClaim ClaimTypeReferenceId="isLocalUser" DefaultValue="true" AlwaysUseDefaultValue="true" />
                    </OutputClaims>
                    <IncludeTechnicalProfile ReferenceId="AAD-Common" />
                </TechnicalProfile>