Configure MFA only for first login in B2C custom policy

504 Views Asked by At

I have setup sign-in with username flow with phoneORemial MFA option using sample but it is asking for MFA on each login, i want MFA only for first time login and for migrated user. also is there a way i can toggle MFA feature for specific flow in custom policy?

i tried to change the <Precondition Type="ClaimEquals" ExecuteActionsIf="false"> to true but then it distrubs the whole flow, if user select phone MFA on first is ask to reset the password and if select email MFA it show phone MFA.

Orcestation steps:

<UserJourneys>
<UserJourney Id="SignUpOrSignInMFAOption" DefaultCpimIssuerTechnicalProfileReferenceId="JwtIssuer">
  <OrchestrationSteps>
    <OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
      <ClaimsProviderSelections>
        <ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninUsernameExchange" />
        <ClaimsProviderSelection TargetClaimsExchangeId="ForgotPasswordExchange" />
        <ClaimsProviderSelection TargetClaimsExchangeId="SelfAssertedUsernameDiscoveryExchange" />
      </ClaimsProviderSelections>
      <ClaimsExchanges>
        <ClaimsExchange Id="LocalAccountSigninUsernameExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Username" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <OrchestrationStep Order="2" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
          <Value>objectId</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="SignUpWithLogonUsernameExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonName" />
        <ClaimsExchange Id="ForgotPasswordExchange" TechnicalProfileReferenceId="ForgotPassword" />
        <ClaimsExchange Id="SelfAssertedUsernameDiscoveryExchange" TechnicalProfileReferenceId="SelfAsserted-UsernameDiscovery" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <OrchestrationStep Order="3" Type="InvokeSubJourney">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
          <Value>isForgotPassword</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <JourneyList>
        <Candidate SubJourneyReferenceId="PasswordReset" />
      </JourneyList>
    </OrchestrationStep>

    <OrchestrationStep Order="4" Type="InvokeSubJourney">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
          <Value>userMessage</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="SelfAssertedUserMessageExchange" TechnicalProfileReferenceId="SelfAsserted-UserMessage" />
      </ClaimsExchanges>
      <JourneyList>
        <Candidate SubJourneyReferenceId="RestoreUsername" />
      </JourneyList>
    </OrchestrationStep>

    <!-- This step reads any user attributes that we may not have received when authenticating using ESTS so they can be sent 
      in the token. -->
    <OrchestrationStep Order="5" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>authenticationSource</Value>
          <Value>socialIdpAuthentication</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <OrchestrationStep Order="6" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
          <Value>extension_mfaByPhoneOrEmail</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="SelfAsserted-Select-MFA-Method" TechnicalProfileReferenceId="SelfAsserted-Select-MFA-Method" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <!-- Throw error if control was bypassed -->
    <OrchestrationStep Order="7" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="Return-MFA-Method-Incorrect-Error">
      <Preconditions>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>extension_mfaByPhoneOrEmail</Value>
          <Value>email</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>extension_mfaByPhoneOrEmail</Value>
          <Value>phone</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
    </OrchestrationStep>

    <!-- Phone verification: If MFA is not required, the next three steps (#5-#7) should be removed.
         This step checks whether there's a phone number on record,  for the user. If found, then the user is challenged to verify it. -->
    <OrchestrationStep Order="8" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
          <Value>isActiveMFASession</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        <!-- If the preferred MFA method is not 'phone' skip this orchestration step-->
        <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
          <Value>extension_mfaByPhoneOrEmail</Value>
          <Value>phone</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. -->
    <OrchestrationStep Order="9" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
          <Value>newPhoneNumberEntered</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="AADUserWriteWithObjectId" TechnicalProfileReferenceId="AAD-UserWritePhoneNumberUsingObjectId" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <!-- MFA with email-->
    <OrchestrationStep Order="10" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
          <Value>extension_mfaByPhoneOrEmail</Value>
          <Value>email</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="Email-Verify" TechnicalProfileReferenceId="EmailVerifyOnSignIn" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <!-- check if change password is required. If yes, ask the user to reset the password-->
    <OrchestrationStep Order="11" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>authenticationSource</Value>
          <Value>socialIdpAuthentication</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>skipPasswordReset</Value>
          <Value>True</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <OrchestrationStep Order="12" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
  </OrchestrationSteps>
  <ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>

Thanking in anticipation.

UPDATE

have update the step 8 with this

<OrchestrationStep Order="8" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
          <Value>isActiveMFASession</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        <!-- If the preferred MFA method is not 'phone' skip this orchestration step-->
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>extension_mfaByPhoneOrEmail</Value>
          <Value>phone</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="PhoneFactor-Verify" TechnicalProfileReferenceId="PhoneFactor-InputOrVerify" />
      </ClaimsExchanges>
    </OrchestrationStep>

but now when user select phone as MFA it logsin the user with doing OTP verification.

1

There are 1 best solutions below

0
Chad Hasbrook On

During migration, create an extension attribute called extension_IsMigrated boolean that writes True after a just-in-time migration (JIT).

Define how 'first login' for your user based on claims. This could be a claim being populated in the directory, some information collected (like terms and conditions), just something that you can track

Have a precondition that skips MFA based on either ClaimsExist or ClaimsEquals for both claims. For example: you can look to see extension_IsMigrated ClaimsEquals 'False' then, SkipThisOrchestrationStep.

If you can assume that every user that first logged in also has performed MFA then, you can read the strongauthentication claim in the directory as a 'read' operation and use this as a precondition prior to triggering the MFA orchestration step.