如何在每次登录后添加邮箱验证?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验证的流程,需确保:
- 先完成本地账号的密码验证,再执行邮箱OTP步骤
- 使用Azure AD B2C内置的标准邮箱验证技术档案,避免自定义错误
- 确保邮箱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>
关键说明
- 使用标准验证技术档案:
LocalAccount-EmailVerification是Azure AD B2C内置的邮箱OTP验证组件,会自动处理OTP发送、用户输入验证逻辑,无需自定义实现。 - Claim传递:在登录步骤输出
emailClaim,步骤2通过AAD-UserReadUsingObjectId读取用户邮箱后,步骤3的标准验证组件会自动使用该邮箱发送OTP。 - 流程顺序:严格遵循「密码验证→读取用户信息→邮箱OTP验证→发令牌」的顺序,避免初始配置中验证前置的问题。
内容的提问来源于stack exchange,提问作者Iria
相关产品推荐
相关产品推荐

