In case anyone faces similar issue, I found out why MFA selection screen keeps appearing for TOTP. The reason is that, once you select your desired MFA on orchestration step 4, using SelfAsserted-Select-MFA-Method
it doesn't persist the value. It happens after, for example, if you have selected email
as your MFA option, Technical Profile EmailVerifyOnSignIn
used during code verification has a following reference:
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteMFAMethod"></ValidationTechnicalProfile>
</ValidationTechnicalProfiles>
UserWriteMFAMethod
actually saves your value and is not being referenced by TOTP related flow.
So the fix would be following, since I want to save selected MFA method only after user has made a verification, I edited OTPVerification
like this:
<TechnicalProfile Id="OTPVerification">
<DisplayName>Sign in with Authenticator app</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.selfasserted.totp</Item>
<Item Key="language.button_continue">Verify</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims></InputClaims>
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="QrCodeVerifyInstruction" />
<DisplayClaim ClaimTypeReferenceId="otpCode" Required="true" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="otpCode" Required="true" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AzureMfa-VerifyOTP" />
<!-- THIS WAS ADDED TO SAVE SELECTED MFA METHOD -->
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteMFAMethod" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-MFA-TOTP" />
</TechnicalProfile>
Then you can reference your extension claim to skip TOTP steps:
<!-- Call the TOTP enrollment ub journey. If user already enrolled the sub journey will not ask the user to enroll -->
<OrchestrationStep Order="8" Type="InvokeSubJourney">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>extension_mfaByPhoneOrEmail</Value>
<Value>totpApp</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<JourneyList>
<Candidate SubJourneyReferenceId="TotpFactor-Input" />
</JourneyList>
</OrchestrationStep>
<!-- Call the TOTP validation sub journey-->
<OrchestrationStep Order="9" Type="InvokeSubJourney">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>extension_mfaByPhoneOrEmail</Value>
<Value>totpApp</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<JourneyList>
<Candidate SubJourneyReferenceId="TotpFactor-Verify" />
</JourneyList>
</OrchestrationStep>
Provided examples are mainly from Microsoft Authenticator TOTP and MFA with either Phone (Call/SMS) or Email verification, merged together.