5

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>
Sam Joshua
  • 310
  • 6
  • 17
Alboz
  • 1,833
  • 20
  • 29
  • Does it work if you remove the embedded forgot password parts from this user journey? – Jas Suri - MSFT May 29 '21 at 08:40
  • @JasSuri-MSFT Yes removing it fixes the issue. What's the relation? We followed the recommended approach for Password Reset. – Alboz May 31 '21 at 14:16
  • @JasSuri-MSFT shall we revert back to the old legacy Password Reset way? – Alboz May 31 '21 at 14:25
  • Thanks for testing that, not sure the root cause but now I know where to investigate. Yes you could use legacy password reset for now as a mitigation. – Jas Suri - MSFT May 31 '21 at 22:25
  • Seems that the issue has been resolved right? Can you post an answer @JasSuri-MSFT? Thank you! – Allen Wu Jun 01 '21 at 02:35
  • No I don’t think so, it should work like the OP, but instead it only works the legacy way. – Jas Suri - MSFT Jun 01 '21 at 07:21
  • @JasSuri-MSFT just to make it clear: This problem only happens if the user resets the password using the Built-In Password Reset User Flow. If the user resets the password using the "Embedded Password Reset", everything works fine. However, we don't have a way to send an email link directly to the Embedded Password Reset, thus we created a user flow and send an email with a link to it (for first login). – Alboz Jun 01 '21 at 12:53
  • Not following the connection between there being two policies at play. From the description, it would seem that this policy would never work for sign in. – Jas Suri - MSFT Jun 01 '21 at 17:55
  • @JasSuri-MSFT On sign-up (done automatically via the GraphAPI), we send a link on an email that takes the user directly into the "Password Reset User Flow" which is a built-in user flow. This is a one-off, to push the user to reset their password. From that moment onwards, for sign-in, we use a Custom Policy which I have shared. Is this clear? Is there another way we can share more details? – Alboz Jun 02 '21 at 09:13
  • @JasSuri-MSFT even if we remove the "built-in" Password Reset user flow, which leaves us with only One Custom Policy with "Self-Served Password Reset" then the same error occurs. After user creation using the Graph API, the user goes through the Self-Served Password Reset (embedded in the custom policy), and then the same error as described happens. If we remove the Self-Served Password reset from the custom policy and use a built-in user flow for Password reset everything works. So the issue seems to be the presence of the Self-Served Password Reset. Did u find anything about it? – Alboz Jun 07 '21 at 18:53

1 Answers1

0

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.

Alboz
  • 1,833
  • 20
  • 29
  • @JasSuri-MSFT Do you have any idea? Why would this make it work? – Alboz Jun 08 '21 at 15:51
  • Is there any chance you have `HRDSignUpSignInMFAEmebeddedPasswordReset` defined in more than one file? And no, the change you made does not add up to me. – Jas Suri - MSFT Jun 14 '21 at 19:22
  • @JasSuri-MSFT You mean in more than one file within one Custom Policy Hierarchy? In that case no. But we have copies of the 'custom policy' and there we have the same Journey Id. We are seeing this error now: AADB2C90051: No suitable claims providers were found. Correlation+ID:+c014004a-d2da-4000-83e5-6d648f9acccc Timestamp:+2021-06-16+07:17:16Z Any debug message you can see to help with this? – Alboz Jun 16 '21 at 07:23