AD B2C TOTP Custom policy and Google IDP

Tiago Catarino 40 Reputation points
2025-04-29T10:58:20.98+00:00

I am currently using this custom policy sample: https://github.com/azure-ad-b2c/samples/tree/master/policies/totp This sample seems to work perfectly when signing in with a local account. In the sample (in this custom policy sample it is only shown the example of how to set it up for a local account).**
**
this is my User Journey (this is workin with a local account login)

    <UserJourney Id="SignUpOrSignInTOTP" DefaultCpimIssuerTechnicalProfileReferenceId="JwtIssuer">
      <OrchestrationSteps>
        <OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
          <ClaimsProviderSelections>
            <ClaimsProviderSelection TargetClaimsExchangeId="MicrosoftAccountExchange" />
            <ClaimsProviderSelection TargetClaimsExchangeId="GoogleExchange" />
            <ClaimsProviderSelection TargetClaimsExchangeId="AppleExchange" />
            <ClaimsProviderSelection TargetClaimsExchangeId="TwitterExchange"/>  
            <ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
          </ClaimsProviderSelections>
          <ClaimsExchanges>
            <ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="2" Type="ClaimsExchange">
          <Preconditions>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
              <Value>objectId</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsExchanges>
            <ClaimsExchange Id="MicrosoftAccountExchange" TechnicalProfileReferenceId="MSA-OIDC-SignIn" />
            <ClaimsExchange Id="GoogleExchange" TechnicalProfileReferenceId="Google-OAUTH-SignIn" />
            <ClaimsExchange Id="AppleExchange" TechnicalProfileReferenceId="Apple-OAUTH-SignIn" />
            <ClaimsExchange Id="TwitterExchange" TechnicalProfileReferenceId="Twitter-SignIn" />
            <ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail-HasEmailFlagged" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <!-- Subjourney : 
        Handle new social account sign in.

        If LocalAccountEmail does not exist, and is not LocalAccount auth, then this Social Id must be provisioned as normal
        If its a local account auth, then skip this entirely -->
        <OrchestrationStep Order="3" Type="InvokeSubJourney">
          <Preconditions>
            <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
              <Value>authenticationSource</Value>
              <Value>localAccountAuthentication</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
            <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
              <Value>LocalAccountEmail</Value>
              <Value>false</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
            <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
              <Value>authenticationSource</Value>
              <Value>localAccountAuthentication</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <JourneyList>
            <Candidate SubJourneyReferenceId="ProvisionOrSignInNewSocialAccountTOTP" />
          </JourneyList>
        </OrchestrationStep>
        <!-- if social account auth, try find an account with the incoming email, and return the LocalAccountEmail-->
        <OrchestrationStep Order="4" Type="ClaimsExchange">
          <Preconditions>
            <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
              <Value>authenticationSource</Value>
              <Value>localAccountAuthentication</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsExchanges>
            <ClaimsExchange Id="FindLocalAccountWithSocialEmail" TechnicalProfileReferenceId="AAD-FindLocalAccountWithSocialEmail" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="5" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <!-- Call the TOTP enrollment ub journey. If user already enrolled the sub journey will not ask the user to enroll -->
        <OrchestrationStep Order="6" Type="InvokeSubJourney">
          <JourneyList>
            <Candidate SubJourneyReferenceId="TotpFactor-Input" />
          </JourneyList>
        </OrchestrationStep>
        <!-- Call the TOTP validation sub journey-->
        <OrchestrationStep Order="7" Type="InvokeSubJourney">
          <JourneyList>
            <Candidate SubJourneyReferenceId="TotpFactor-Verify" />
          </JourneyList>
        </OrchestrationStep>
        <OrchestrationStep Order="8" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
      </OrchestrationSteps>
    </UserJourney>

**
Expected Behavior:**
When the user signs in with a provider like google or twitter, they are displayed a QR Code to enable TOTP.

Current Behavior:
After attempting to sign-in with Google the following error is shown instead of the QR code page:
User's image

Any help with this is welcomed :)

Microsoft Security | Microsoft Entra | Microsoft Entra ID
{count} votes

1 answer

Sort by: Most helpful
  1. Tiago Catarino 40 Reputation points
    2025-05-08T14:23:13.4366667+00:00

    The solution to the issue was adding this orchestration step:

            <OrchestrationStep Order="3" Type="ClaimsExchange">
              <Preconditions>
                <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
                  <Value>authenticationSource</Value>
                  <Value>localAccountAuthentication</Value>
                  <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
              </Preconditions>
              <ClaimsExchanges>
                <ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId" />
              </ClaimsExchanges>
            </OrchestrationStep>
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.