You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在每次登录后添加邮箱验证?Azure AD B2C策略配置问题

需求场景

仅登录策略需实现以下流程:

  • 用户输入邮箱和密码;
  • 用户登录邮箱收到OTP;
  • 用户输入OTP完成验证;
  • 用户登录并获取令牌。

初始尝试配置

使用入门包中的策略,初始UserJourney配置如下:

<UserJourney Id="SignIn">
            <OrchestrationSteps>
                <OrchestrationStep Order="1"
                                   Type="CombinedSignInAndSignUp"
                                   ContentDefinitionReferenceId="api.localaccountsignin">
                    <ClaimsProviderSelections>
                        <ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
                    </ClaimsProviderSelections>
                    <ClaimsExchanges>
                        <ClaimsExchange Id="LocalAccountSigninEmailExchange"
                                        TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
                    </ClaimsExchanges>
                </OrchestrationStep>
                <!-- This step reads any user attributes that we may not have received when in the token. -->
                <OrchestrationStep Order="2"
                                   Type="ClaimsExchange">
                    <ClaimsExchanges>
                        <ClaimsExchange Id="AADUserReadWithObjectId"
                                        TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
                    </ClaimsExchanges>
                </OrchestrationStep>
                <OrchestrationStep Order="3"
                                   Type="SendClaims"
                                   CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
                <!-- Track that we have successfully sent a token -->
            </OrchestrationSteps>

修改后的SelfAsserted-LocalAccountSignin-Email技术档案:

<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
                <DisplayName>Local Account Signin</DisplayName>
                <Protocol Name="Proprietary"
                          Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
                <Metadata>
                    <Item Key="SignUpTarget">SignUpWithLogonEmailExchange</Item>
                    <Item Key="setting.operatingMode">Email</Item>
                    <Item Key="ContentDefinitionReferenceId">api.localaccountsignin</Item>
                    <Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
                </Metadata>
                <IncludeInSso>false</IncludeInSso>
                <InputClaims>
                    <InputClaim ClaimTypeReferenceId="signInName"
                                DefaultValue="{OIDC:LoginHint}"
                                AlwaysUseDefaultValue="true" />
                    <!--InputClaim ClaimTypeReferenceId="email" /-->
                </InputClaims>
                <OutputClaims>
                    <!--OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" /-->
                    <OutputClaim ClaimTypeReferenceId="signInName"
                                 Required="true" />
                    <OutputClaim ClaimTypeReferenceId="password"
                                 Required="true" />
                    <OutputClaim ClaimTypeReferenceId="objectId" />
                    <OutputClaim ClaimTypeReferenceId="authenticationSource" />
                </OutputClaims>
                <ValidationTechnicalProfiles>
                    <ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
                </ValidationTechnicalProfiles>
                <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
            </TechnicalProfile>

初始问题

配置后出现流程错误:用户先验证邮箱,再输入用户名和密码,需两次输入邮箱;且验证步骤在认证之前。

更新配置及新问题

更新后的UserJourney:

<UserJourney Id="SignUpOrSignInMFAOption" DefaultCpimIssuerTechnicalProfileReferenceId="JwtIssuer">
  <OrchestrationSteps>
    <OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
      <ClaimsProviderSelections>
        <!-- <ClaimsProviderSelection TargetClaimsExchangeId="FacebookExchange"/> -->
        <ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
      </ClaimsProviderSelections>
      <ClaimsExchanges>
        <ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
      </ClaimsExchanges>
    </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="2" 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>

    <!--Sample: If uses is enrolled for MFA, ask the user to select the preferred method-->

    <!-- Throw error if control was bypassed -->

    <!-- 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. -->

    <!-- 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. -->

    <!--Sample: MFA with email-->
    <OrchestrationStep Order="3" Type="ClaimsExchange">
      <ClaimsExchanges>
        <ClaimsExchange Id="Email-Verify" TechnicalProfileReferenceId="EmailVerifyOnSignIn" />
      </ClaimsExchanges>
    </OrchestrationStep>

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

对应的EmailVerifyOnSignIn技术档案:

<TechnicalProfile Id="EmailVerifyOnSignIn">
      <DisplayName>EmailVerifyOnSignIn</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</Item>
      </Metadata>
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="readOnlyEmail" />
      </InputClaims>
      <OutputClaims>
        <!-- Required claims -->
        <OutputClaim ClaimTypeReferenceId="isActiveMFASession" DefaultValue="true" />
        <OutputClaim ClaimTypeReferenceId="readOnlyEmail" PartnerClaimType="Verified.Email" />
      </OutputClaims>
      <ValidationTechnicalProfiles>
        <ValidationTechnicalProfile ReferenceId="AAD-UserWriteMFAMethod"></ValidationTechnicalProfile>
      </ValidationTechnicalProfiles>
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-MFA" />
    </TechnicalProfile>

更新配置后系统崩溃,无法触发邮箱验证。用户无需自定义邮箱验证,仅需使用标准验证方式,寻求解决方法。

解决方案

核心调整思路

要实现先验证邮箱密码,再触发邮箱OTP验证的流程,需确保:

  1. 先完成本地账号的密码验证,再执行邮箱OTP步骤
  2. 使用Azure AD B2C内置的标准邮箱验证技术档案,避免自定义错误
  3. 确保邮箱Claim能正确从用户目录读取并传递到验证步骤

修正后的UserJourney配置

<UserJourney Id="SignInWithEmailOTP">
  <OrchestrationSteps>
    <!-- 步骤1:用户输入邮箱和密码完成验证 -->
    <OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.localaccountsignin">
      <ClaimsProviderSelections>
        <ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
      </ClaimsProviderSelections>
      <ClaimsExchanges>
        <ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <!-- 步骤2:读取用户目录中的邮箱信息 -->
    <OrchestrationStep Order="2" 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>

    <!-- 步骤3:调用标准邮箱OTP验证 -->
    <OrchestrationStep Order="3" Type="ClaimsExchange">
      <ClaimsExchanges>
        <ClaimsExchange Id="EmailVerification" TechnicalProfileReferenceId="LocalAccount-EmailVerification" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <!-- 步骤4:发送令牌 -->
    <OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
  </OrchestrationSteps>
</UserJourney>

修正SelfAsserted-LocalAccountSignin-Email技术档案

确保登录步骤能正确输出email Claim,供后续验证使用:

<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
  <DisplayName>Local Account Signin</DisplayName>
  <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  <Metadata>
    <Item Key="SignUpTarget">SignUpWithLogonEmailExchange</Item>
    <Item Key="setting.operatingMode">Email</Item>
    <Item Key="ContentDefinitionReferenceId">api.localaccountsignin</Item>
    <Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
  </Metadata>
  <IncludeInSso>false</IncludeInSso>
  <InputClaims>
    <InputClaim ClaimTypeReferenceId="signInName" DefaultValue="{OIDC:LoginHint}" AlwaysUseDefaultValue="true" />
  </InputClaims>
  <OutputClaims>
    <OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
    <OutputClaim ClaimTypeReferenceId="password" Required="true" />
    <OutputClaim ClaimTypeReferenceId="objectId" />
    <OutputClaim ClaimTypeReferenceId="authenticationSource" />
    <!-- 新增:输出email Claim,用于后续验证 -->
    <OutputClaim ClaimTypeReferenceId="email" />
  </OutputClaims>
  <ValidationTechnicalProfiles>
    <ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
  </ValidationTechnicalProfiles>
  <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>

关键说明

  1. 使用标准验证技术档案:LocalAccount-EmailVerification是Azure AD B2C内置的邮箱OTP验证组件,会自动处理OTP发送、用户输入验证逻辑,无需自定义实现。
  2. Claim传递:在登录步骤输出email Claim,步骤2通过AAD-UserReadUsingObjectId读取用户邮箱后,步骤3的标准验证组件会自动使用该邮箱发送OTP。
  3. 流程顺序:严格遵循「密码验证→读取用户信息→邮箱OTP验证→发令牌」的顺序,避免初始配置中验证前置的问题。

内容的提问来源于stack exchange,提问作者Iria

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 23:55:21