Azure AD B2C: Cannot login immediately after going through a Password Reset Flow

1.2k Views Asked by At

We have created a built-in Password Reset user flow.

We register users automatically in B2C using the Microsoft Graph API and send an email with a direct link to the Password Reset flow for them to reset the password on the first login.

The user goes through the Password Reset user flow correctly, and it gets redirected back to our application, which redirects the user to our SignIn custom policy user journey!

We have Home Realm Discovery where the user is first presented with a screen to enter their email address, clicks Next, and later enters the password.

After entering the email address and clicking "Next" we get the following error:

Sorry, but we're having trouble signing you in.
We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, please try again.

Correlation ID: d5a7e1ed-a6d2-4b6d-bc87-b8612a5419b4

Timestamp: 2021-05-27 12:19:05Z

AADB2C: An exception has occurred.

Here is the UserJourney and SubJourney:

<UserJourneys>
<UserJourney Id="HRDSignUpSignInMFAEmebeddedPasswordReset">
  <OrchestrationSteps>

    <OrchestrationStep Order="1" Type="ClaimsExchange">
      <ClaimsExchanges>
        <ClaimsExchange Id="ParseDomainHint" TechnicalProfileReferenceId="ParseDomainHint" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <!-- api.hrd reference to custom login page / content definition -->
    <OrchestrationStep Order="2" Type="ClaimsExchange" ContentDefinitionReferenceId="api.hrd">
      <Preconditions>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>isFederatedAuthentication</Value>
          <Value>true</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="SigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-Signin-Email" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <OrchestrationStep Order="3" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>isFederatedAuthentication</Value>
          <Value>False</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="IsKnownCustomerLogic" TechnicalProfileReferenceId="CreateidentityProvidersCollectionLogic" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <!-- The technical profile uses a validation technical profile to authenticate the user. -->
    <!--Protocal: Web.TPEngine.Providers.SelfAssertedAttributeProvider Session: SM-AAD=Web.TPEngine.SSO.DefaultSSOSessionProvider -->
    <OrchestrationStep Order="4" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signinandsignupwithpassword">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
          <Value>objectId</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>isFederatedAuthentication</Value>
          <Value>true</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsProviderSelections>
        <ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
        <ClaimsProviderSelection TargetClaimsExchangeId="ForgotPasswordExchange" />
      </ClaimsProviderSelections>
      <ClaimsExchanges>
        <ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <!-- Local Email Account Sign Up -->
    <!-- Protocol: Web.TPEngine.Providers.SelfAssertedAttributeProvider  Session: SM-AAD="Web.TPEngine.SSO.DefaultSSOSessionProvider -->
    <OrchestrationStep Order="5" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
          <Value>objectId</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>isFederatedAuthentication</Value>
          <Value>true</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
        <ClaimsExchange Id="ForgotPasswordExchange" TechnicalProfileReferenceId="ForgotPassword" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <!-- If the domain matched any known domain, then this step will have a single IdP
            enabled due to each known IdP TP having an enablement flag via identityProviders claim -->
    <OrchestrationStep Order="6" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.idpselections">
      <Preconditions>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
          <Value>isFederatedAuthentication</Value>
          <Value>true</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsProviderSelections>
        <ClaimsProviderSelection TargetClaimsExchangeId="IDP1OIDC" />
        <ClaimsProviderSelection TargetClaimsExchangeId="IDP2SAML" />
      </ClaimsProviderSelections>
    </OrchestrationStep>

    <OrchestrationStep Order="7" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
          <Value>objectId</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
          <Value>isFederatedAuthentication</Value>
          <Value>true</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="IDP1OIDC" TechnicalProfileReferenceId="IDP1-OIDC-TP" />
        <ClaimsExchange Id="IDP2SAML" TechnicalProfileReferenceId="IDP2-SAML-TP" />
      </ClaimsExchanges>
    </OrchestrationStep>

     <!-- If the user clicks on Forgot Password then execute this subjourney - otherwise skip -->
     <OrchestrationStep Order="8" Type="InvokeSubJourney">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
          <Value>isForgotPassword</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <JourneyList>
        <Candidate SubJourneyReferenceId="PasswordReset" />
      </JourneyList>
    </OrchestrationStep>

    <!-- For social IDP authentication, attempt to find the user account in the directory. -->
    <OrchestrationStep Order="9" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
          <Value>objectId</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>authenticationSource</Value>
          <Value>localAccountAuthentication</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <OrchestrationStep Order="10" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
          <Value>objectId</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
      </ClaimsExchanges>
    </OrchestrationStep>
    <!-- The previous step (SelfAsserted-Social) could have been skipped if there were no attributes to collect 
         from the user. So, in that case, create the user in the directory if one does not already exist 
         (verified using objectId which would be set from the last step if account was created in the directory. -->
    <OrchestrationStep Order="11" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
          <Value>objectId</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
          <Value>authenticationSource</Value>
          <Value>socialIdpAuthentication</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
      </ClaimsExchanges>
    </OrchestrationStep>
    <OrchestrationStep Order="12" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
          <Value>isActiveMFASession</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>isFederatedAuthentication</Value>
          <Value>true</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="PhoneFactor-Verify" TechnicalProfileReferenceId="PhoneFactor-InputOrVerify" />
      </ClaimsExchanges>
    </OrchestrationStep>
    <!-- Save MFA phone number: The precondition verifies whether the user provided a new number in the 
            previous step. If so, then the phone number is stored in the directory for future authentication 
            requests. -->
    <!--References AAD-Common(Web.TPEngine.Providers.AzureActiveDirectoryProvider & SM-Noop) -->
    <OrchestrationStep Order="13" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>isFederatedAuthentication</Value>
          <Value>true</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
          <Value>newPhoneNumberEntered</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="AADUserWriteWithObjectId" TechnicalProfileReferenceId="AAD-UserWritePhoneNumberUsingObjectId" />
      </ClaimsExchanges>
    </OrchestrationStep>
    <OrchestrationStep Order="14" Type="ClaimsExchange">
      <ClaimsExchanges>
        <!-- create the emails claim combining signInNames and otherMails -->
        <ClaimsExchange Id="AADUserCreateEmailsClaim" TechnicalProfileReferenceId="AAD-UserCreateEmailsClaim" />
      </ClaimsExchanges>
    </OrchestrationStep>
    <OrchestrationStep Order="15" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
  </OrchestrationSteps>
  <ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
<SubJourneys>
    <SubJourney Id="PasswordReset" Type="Call">
      <OrchestrationSteps>
        <!--Sample: Validate user's email address. Run this step only when user resets the password-->
        <OrchestrationStep Order="1" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="PasswordResetUsingEmailAddressExchange" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingEmailAddress" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <!--Sample: Collect and persist a new password. Run this step only when user resets the password-->
        <OrchestrationStep Order="2" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId" />
          </ClaimsExchanges>
        </OrchestrationStep>
      </OrchestrationSteps>
    </SubJourney>
  </SubJourneys>
1

There are 1 best solutions below

3
On

This is not a Full Answer/Fix.

I don't know what is wrong with the above User Journey, but according to what @JasSuri-MSFT writes here https://github.com/azure-ad-b2c/samples/issues/235 the error message says:

Claims exchange with id 'LocalAccountSigninEmailExchange' could not be found 
in orchestration step '5' and the step contains more than one claims exchange.

Then all I did is to move the <ClaimExchange> for LocalAccountSigninEmailExchange from step 4 to step 5. Now Step 4 and 5 look like this:

 <!-- The technical profile uses a validation technical profile to authenticate the user. -->
        <!--Protocal: Web.TPEngine.Providers.SelfAssertedAttributeProvider Session: SM-AAD=Web.TPEngine.SSO.DefaultSSOSessionProvider -->
        <OrchestrationStep Order="4" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signinandsignupwithpassword">
          <Preconditions>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
              <Value>objectId</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
            <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
              <Value>isFederatedAuthentication</Value>
              <Value>true</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsProviderSelections>
            <ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
            <ClaimsProviderSelection TargetClaimsExchangeId="ForgotPasswordExchange" />
          </ClaimsProviderSelections>
          <ClaimsExchanges>
            <!-- <ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" /> -->
          </ClaimsExchanges>
        </OrchestrationStep>
        <!-- Local Email Account Sign Up -->
        <!-- Protocol: Web.TPEngine.Providers.SelfAssertedAttributeProvider  Session: SM-AAD="Web.TPEngine.SSO.DefaultSSOSessionProvider -->
        <OrchestrationStep Order="5" Type="ClaimsExchange">
          <Preconditions>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
              <Value>objectId</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
            <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
              <Value>isFederatedAuthentication</Value>
              <Value>true</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsExchanges>
            <ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
            <ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
            <ClaimsExchange Id="ForgotPasswordExchange" TechnicalProfileReferenceId="ForgotPassword" />
          </ClaimsExchanges>
        </OrchestrationStep>

This is different from the User Guide and the B2C Sample in Github, but for some reason now it works. That said, I have very low confidence as I don't understand why, but thought I'd still share it with you.