如何在Azure B2C自定义策略中传递并验证外部IDP登录的signInEmail声明
嘿,看起来你在AAD B2C的登录流程里碰到了个头疼的问题——调用REST-ValidateSignInEmail技术配置文件时,你的REST API总是因为没收到期望的UserEmail声明返回"is disabled"错误。我帮你梳理下排查和解决的思路:
一、先确认登录后B2C的Claims里有没有可用的邮箱值
不同身份提供商(IdP)返回的声明字段不一样,你以为的signInName不一定会自动出现在B2C的Claims集合里:
- Facebook通常返回的邮箱字段是
email - 微软账户可能返回
preferred_username或者email - 企业AD一般返回
userPrincipalName或email
所以第一步得搞清楚,用户通过这三个IdP登录后,B2C到底拿到了哪些和邮箱相关的声明。
二、调整REST技术配置文件的输入声明
如果signInName这个声明根本不存在,那肯定传不过去,你可以选两种方式调整:
方案1:直接用IdP返回的原始邮箱声明
修改REST-ValidateSignInEmail里的<InputClaims>,比如针对Facebook/微软账户,直接用email声明:
<InputClaims> <InputClaim ClaimTypeReferenceId="email" PartnerClaimType="UserEmail" /> </InputClaims>
如果是企业AD,就用userPrincipalName:
<InputClaims> <InputClaim ClaimTypeReferenceId="userPrincipalName" PartnerClaimType="UserEmail" /> </InputClaims>
方案2:统一映射成signInName声明
如果你想统一用signInName这个字段,那就加个声明转换步骤,把不同IdP的邮箱字段都转成signInName:
- 先确保策略里已经定义了
signInName声明类型(如果还没的话):
<ClaimType Id="signInName"> <DisplayName>Sign In Name</DisplayName> <DataType>string</DataType> </ClaimType>
- 添加一个声明转换规则:
<ClaimsTransformation Id="ConvertEmailToSignInName" TransformationMethod="FormatStringClaim"> <InputClaims> <InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim" /> </InputClaims> <OutputClaims> <OutputClaim ClaimTypeReferenceId="signInName" TransformationClaimType="outputClaim" /> </OutputClaims> <InputParameters> <InputParameter Id="stringFormat" Value="{0}" /> </InputParameters> </ClaimsTransformation>
- 把这个转换规则加到对应的IdP技术配置文件里,比如Facebook的:
<TechnicalProfile Id="Facebook-OAUTH"> <!-- 其他原有配置 --> <OutputClaimsTransformations> <OutputClaimsTransformation ReferenceId="ConvertEmailToSignInName" /> </OutputClaimsTransformations> </TechnicalProfile>
这样不管哪个IdP登录,signInName都会有值,后续调用REST API就能正常传递了。
三、修正编排步骤的前置条件
你现在的编排步骤只对CorporateAD生效,但需求是覆盖Facebook、微软账户和企业AD三种情况。所以得修改前置条件,让这个步骤在这三个IdP登录时都执行:
<OrchestrationStep Order="4" Type="ClaimsExchange"> <Preconditions> <Precondition Type="ClaimEquals" ExecuteActionsIf="false"> <Value>idp</Value> <Value>Facebook</Value> <Action>SkipThisOrchestrationStep</Action> </Precondition> <Precondition Type="ClaimEquals" ExecuteActionsIf="false"> <Value>idp</Value> <Value>MicrosoftAccount</Value> <Action>SkipThisOrchestrationStep</Action> </Precondition> <Precondition Type="ClaimEquals" ExecuteActionsIf="false"> <Value>idp</Value> <Value>CorporateAD</Value> <Action>SkipThisOrchestrationStep</Action> </Precondition> </Preconditions> <ClaimsExchanges> <ClaimsExchange Id="FetchMoreClaimsExchange" TechnicalProfileReferenceId="REST-ValidateSignInEmail" /> </ClaimsExchanges> </OrchestrationStep>
或者更简洁的写法——排除不需要执行这个步骤的IdP(比如本地账户):
<Preconditions> <Precondition Type="ClaimNotEquals" ExecuteActionsIf="true"> <Value>idp</Value> <Value>LocalAccount</Value> <Action>SkipThisOrchestrationStep</Action> </Precondition> </Preconditions>
四、临时调试看清楚所有声明
要是还是不确定哪些声明存在,可以加个临时的显示步骤,把当前的Claims都展示出来:
<OrchestrationStep Order="3" Type="ClaimsExchange"> <ClaimsExchanges> <ClaimsExchange Id="DebugDisplayClaims" TechnicalProfileReferenceId="SelfAsserted-Debug" /> </ClaimsExchanges> </OrchestrationStep>
然后定义对应的调试技术配置文件:
<TechnicalProfile Id="SelfAsserted-Debug"> <DisplayName>Debug Claims</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> <DisplayClaims> <DisplayClaim ClaimTypeReferenceId="idp" /> <DisplayClaim ClaimTypeReferenceId="email" /> <DisplayClaim ClaimTypeReferenceId="signInName" /> <DisplayClaim ClaimTypeReferenceId="userPrincipalName" /> </DisplayClaims> <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" /> </TechnicalProfile>
这样登录时会弹出页面显示这些声明的值,你就能一目了然知道该用哪个字段传给REST API了。
内容的提问来源于stack exchange,提问作者Leniel Maccaferri

