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

Azure B2C注册流程:点击发送验证码时提示邮箱已占用

Azure B2C自定义策略:点击发送验证码前校验邮箱是否已注册

问题背景

现有仅支持注册的Azure B2C自定义策略,包含自定义邮箱Display Control。当前逻辑是用户完成邮箱验证后,才会收到「该邮箱已被占用」的提示,用户资料与密码信息在后续步骤收集,需优化校验时机。

当前实现代码

<TechnicalProfile Id="PartnerSignUpVerifyEmailPage">
    <DisplayName>Local Email Verification</DisplayName>
    <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    <Metadata>
        <Item Key="IpAddressClaimReferenceId">IpAddress</Item>
        <Item Key="ContentDefinitionReferenceId">api.partners.signUpVerifyEmailPage</Item>
        <Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
        <Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">A user with this email address already exists.</Item>
    </Metadata>
    <InputClaimsTransformations>
        <InputClaimsTransformation ReferenceId="GetLocalizedStringsForEmail" />
    </InputClaimsTransformations>
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="email" DefaultValue="{OIDC:LoginHint}" AlwaysUseDefaultValue="true" />
    </InputClaims>
    <DisplayClaims>
        <DisplayClaim DisplayControlReferenceId="localizedSignUpEmailVerificationControl" />
    </DisplayClaims>
    <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="email" Required="true" />
    </OutputClaims>
    <ValidationTechnicalProfiles>
        <!-- this validation asserts the email provided isn't already in use -->
        <ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress-RaiseIfExists" />
    </ValidationTechnicalProfiles>
    <IncludeTechnicalProfile ReferenceId="AAD-Common" />
    <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>


<DisplayControl Id="localizedSignUpEmailVerificationControl" UserInterfaceControlType="VerificationControl">
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="email" />
    </InputClaims>
    <DisplayClaims>
        <DisplayClaim ClaimTypeReferenceId="email" Required="true" />
        <DisplayClaim ClaimTypeReferenceId="verificationCode" ControlClaimType="VerificationCode" Required="true" />                    
    </DisplayClaims>
    <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="email" />
    </OutputClaims>
    <Actions>
        <Action Id="SendCode">
            <ValidationClaimsExchange>  
                <ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="GenerateOtp"/>
                <ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="SendLocalizedOtp"/>
            </ValidationClaimsExchange>
        </Action>
        <Action Id="VerifyCode">
            <ValidationClaimsExchange>
                <ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="VerifyOtp" />
            </ValidationClaimsExchange>
        </Action>
    </Actions>
</DisplayControl>
</DisplayControls>


<TechnicalProfile Id="AAD-UserReadUsingEmailAddress-RaiseIfExists">
    <Metadata>
        <Item Key="Operation">Read</Item>
        <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
    </Metadata>
    <IncludeInSso>false</IncludeInSso>
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true" />
    </InputClaims>
    <OutputClaims>
        <!-- Required claims -->
        <OutputClaim ClaimTypeReferenceId="objectId" DefaultValue="NOTFOUND" />
        <OutputClaim ClaimTypeReferenceId="objectIdNotFound" DefaultValue="NOTFOUND" AlwaysUseDefaultValue="true" />
    </OutputClaims>
    <OutputClaimsTransformations>
        <!-- ensure that the object id isn't already used -->
        <OutputClaimsTransformation ReferenceId="AssertObjectIdObjectIdNotFoundAreEqual" />
        <!-- blank the object id, in the case that it was used, so we can let the user change the email and retest -->
        <OutputClaimsTransformation ReferenceId="SetObjectIdToNull" />
    </OutputClaimsTransformations>
    <IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile> 

需求目标

用户点击「Send Verification Code」按钮时,先验证该邮箱未被占用;若邮箱已被占用,则显示错误提示,不发送邮件且不推进控件流程。

尝试过的无效方法

方法1:验证、报错并终止

  • 细节:在SendCode的ValidationClaimsExchanges顶部添加AAD-UserReadUsingEmailAddress-RaiseIfExists的ValidationClaimsExchangeTechnicalProfile,为其他ValidationClaimsExchangeTechnicalProfile添加ContinueOnError="false"
  • 结果:邮箱已占用时会显示错误,但控件仍提示发送验证码,且其他验证步骤仍执行并发送邮件。

方法2:评估新Claim并使用前置条件

  • 细节:创建布尔类型Claim emailAlreadyRegistered,在SendCode的ValidationClaimsExchanges顶部添加AAD-UserReadUsingEmailAddress-RaiseIfExists的ValidationClaimsExchangeTechnicalProfile,在该技术配置文件中添加CompareClaims转换以在邮箱匹配时设置emailAlreadyRegistered=True,为其他ValidationClaimsExchangeTechnicalProfile添加基于ClaimEquals的前置条件判断emailAlreadyRegistered是否为True
  • 结果:邮箱已占用时会显示错误,但控件仍提示发送验证码,且其他验证步骤仍执行并发送邮件。

方法3:嵌套验证步骤

  • 细节:创建布尔类型Claim emailAlreadyRegistered,新建Claims Transformation Technical Profile,该配置文件以邮箱为输入并通过转换设置emailAlreadyRegistered,同时包含AAD-UserReadUsingEmailAddress-RaiseIfExists、GenerateOtp、SendLocalizedOtp三个验证技术配置文件;在SendCode的ValidationClaimsExchanges中替换为该新配置文件
  • 结果:邮箱已占用时不显示错误,控件直接进入验证码验证步骤,且未发送邮件。

解决方案(致谢Christian Le Breton)

核心思路

AAD Technical Profile无法设置新Claim,而Claims Technical Profile无法读取AAD数据,因此需要将原有的邮箱校验逻辑拆分为两个独立的技术配置文件,分别完成AAD数据读取和Claim状态设置,再通过前置条件控制后续验证码生成与发送步骤的执行。

实现步骤

  1. 创建布尔类型Claim emailAlreadyRegistered
  2. 将AAD-UserReadUsingEmailAddress-RaiseIfExists拆分为两个独立的Technical Profile:
    • 第一个保留AAD读取与错误断言逻辑,但移除Claim清理和状态设置的输出转换
    • 第二个为ClaimsTransformationProtocolProvider类型的技术配置文件,包含原有的Claim清理转换,并新增CompareClaims转换以设置emailAlreadyRegistered,同时输出默认值为"false"的emailAlreadyRegistered Claim
  3. 在SendCode的ValidationClaimsExchanges顶部添加这两个新的Technical Profile
  4. 为GenerateOtp和SendLocalizedOtp添加基于ClaimEquals的前置条件,当emailAlreadyRegistered为true时跳过当前步骤

最终关键代码

<DisplayControl Id="localizedSignUpEmailVerificationControl" UserInterfaceControlType="VerificationControl">
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="email" />
    </InputClaims>
    <DisplayClaims>
        <DisplayClaim ClaimTypeReferenceId="email" Required="true" />
        <DisplayClaim ClaimTypeReferenceId="verificationCode" ControlClaimType="VerificationCode" Required="true" />
    </DisplayClaims>
    <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="email" />
    </OutputClaims>
    <Actions>
        <Action Id="SendCode">
            <ValidationClaimsExchange>
                <ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="AAD-UserReadUsingEmailAddress-RaiseIfExists-Pt1" ContinueOnError="false" />
                <ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="AAD-UserReadUsingEmailAddress-RaiseIfExists-Pt2" ContinueOnError="false" />
                <ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="GenerateOtp" ContinueOnError="false" >
                    <Preconditions>
                        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
                            <Value>emailAlreadyRegistered</Value>
                            <Value>true</Value>
                            <Action>SkipThisValidationTechnicalProfile</Action>
                        </Precondition>
                    </Preconditions>
                </ValidationClaimsExchangeTechnicalProfile>
                <ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="SendLocalizedOtp" ContinueOnError="false">
                    <Preconditions>
                        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
                            <Value>emailAlreadyRegistered</Value>
                            <Value>true</Value>
                            <Action>SkipThisValidationTechnicalProfile</Action>
                        </Precondition>
                    </Preconditions>
                </ValidationClaimsExchangeTechnicalProfile>
            </ValidationClaimsExchange>
        </Action>
        <Action Id="VerifyCode">
            <ValidationClaimsExchange>
                <ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="VerifyOtp" />
            </ValidationClaimsExchange>
        </Action>
    </Actions>
</DisplayControl>
                
        
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress-RaiseIfExists-Pt1">
    <Metadata>
        <Item Key="Operation">Read</Item>
        <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
    </Metadata>
    <IncludeInSso>false</IncludeInSso>
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true" />
    </InputClaims>
    <OutputClaims>
        <!-- Required claims -->
        <OutputClaim ClaimTypeReferenceId="objectId" DefaultValue="NOTFOUND" />
        <OutputClaim ClaimTypeReferenceId="objectIdNotFound" DefaultValue="NOTFOUND" AlwaysUseDefaultValue="true" />
    </OutputClaims>
    <OutputClaimsTransformations>
        <!-- ensure that the object id isn't already used -->
        <OutputClaimsTransformation ReferenceId="AssertObjectIdObjectIdNotFoundAreEqual" />
    </OutputClaimsTransformations>
    <IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>             
            
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress-RaiseIfExists-Pt2">
    <DisplayName>Check If Email Is Registered</DisplayName>
    <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    <Metadata>
        <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
    </Metadata>
    <IncludeInSso>false</IncludeInSso>
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="objectId" />
        <InputClaim ClaimTypeReferenceId="objectIdNotFound" />
    </InputClaims>
    <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="emailAlreadyRegistered" />
        <OutputClaim ClaimTypeReferenceId="objectId" />
    </OutputClaims>
    <OutputClaimsTransformations>
        <!-- ensure that the object id isn't already used -->
        <OutputClaimsTransformation ReferenceId="SetEmailAlreadyRegistered" />
        <!-- blank the object id, in the case that it was used, so we can let the user change the email and retest -->
        <OutputClaimsTransformation ReferenceId="SetObjectIdToNull" />
    </OutputClaimsTransformations>
</TechnicalProfile>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 09:40:25