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?
Do you have:
as an output claim from SelfAsserted-LocalAccountSignin-Email?
As far as I remember from docs/samples -
userPrincipalName
is required for TOTP SubJourneys to work.