What does a PasswordChange with TOTP for Azure B2C look like?

229 Views Asked by At

I have an existing Azure B2C solution using custom policies and provides sign up / sign in via AAD, MS Account, Google and Local. I'm now adding TOTP MFA as provided by https://learn.microsoft.com/en-us/azure/active-directory-b2c/multi-factor-authentication?pivots=b2c-custom-policy.

I have SignUp/SignIn and Password Reset policies using TOTP working for the Local Account. I don't need TOTP for the social accounts. However, I'm struggling with a ChangePassword policy, where a user who is already logged in can change their password. For this step, I would like the user to verify themselves via TOTP before getting to change the password.

In my non-TOTP solution, I'm using the PasswordChange UserJourney as provided by https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-password-change-policy?pivots=b2c-custom-policy

For completeness, it looks like this:

    <UserJourney Id="PasswordChange">
      <OrchestrationSteps>
        <OrchestrationStep Order="1" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.signuporsignin">
          <ClaimsProviderSelections>
            <ClaimsProviderSelection TargetClaimsExchangeId="LocalAccountSigninEmailExchange" />
          </ClaimsProviderSelections>
        </OrchestrationStep>
        <OrchestrationStep Order="2" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="3" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordChangeUsingObjectId" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="4" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
      </OrchestrationSteps>
      <ClientDefinition ReferenceId="DefaultWeb" />
    </UserJourney>

I was hoping to get away with something similar for PasswordChangeWithTOTP, by inserting the TotpFactor-Input and TotpFactor-Verify steps between steps 2 and 3 of the old policy.

    <UserJourney Id="PasswordChangeWithTOTP">
      <OrchestrationSteps>

        <OrchestrationStep Order="1" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.signuporsignin">
          <ClaimsProviderSelections>
            <ClaimsProviderSelection TargetClaimsExchangeId="LocalAccountSigninEmailExchange" />
          </ClaimsProviderSelections>
        </OrchestrationStep>

        <OrchestrationStep Order="2" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <!-- Call the TOTP enrollment ub journey. If user already enrolled the sub journey will not ask the user to enroll -->
        <OrchestrationStep Order="3" Type="InvokeSubJourney">
          <JourneyList>
            <Candidate SubJourneyReferenceId="TotpFactor-Input" />
          </JourneyList>
        </OrchestrationStep>

        <!-- Call the TOTP validation sub journey-->
        <OrchestrationStep Order="4" Type="InvokeSubJourney">
          <JourneyList>
            <Candidate SubJourneyReferenceId="TotpFactor-Verify" />
          </JourneyList>
        </OrchestrationStep>

        <OrchestrationStep Order="5" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordChangeUsingObjectId" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <OrchestrationStep Order="6" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <OrchestrationStep Order="7" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />

      </OrchestrationSteps>
      <ClientDefinition ReferenceId="DefaultWeb" />
    </UserJourney>

minor talking points: I went with a new policy due to the release cycle of the application using it, and I'm not totally sold that I should offer TOTP registration as a part of this particular step, even though it's something we do for SignUpSignIn and PasswordReset.

The error that I get "The service received a bad request" when performing the "Get available strong authentication devices" activity. The error message has a target type User and Id consistent with similar operations used during sign in. I can't see anything else particularly jumping out at me.

I did try inserting a <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" /> OrchestrationStep before the TotpFactor-Input step, but thought that may have been defeating the purpose of TOTP. I also didn't get prompted to provide a verification code before getting to change the password.

What should a PasswordChange with TOTP policy look like?

1

There are 1 best solutions below

2
On

Do you have:

<OutputClaim ClaimTypeReferenceId="userPrincipalName" />

as an output claim from SelfAsserted-LocalAccountSignin-Email?

As far as I remember from docs/samples - userPrincipalName is required for TOTP SubJourneys to work.