在 B2C 自定义策略中从 Azure AD 获取 singin 电子邮件

Get singin email from Azure AD in a B2C custom policy

在 AAD B2C 中,我创建了具有 2 个身份的用户。我用图形用这个主体创建它们:

{
  "displayName": "John Doe",
  "mail":"johndoe19287456@gmail.com",
  "identities": [
    {
      "signInType": "userName",
      "issuer": "mytenant.onmicrosoft.com",
      "issuerAssignedId": "606198"
    },
    {
      "signInType": "emailAddress",
      "issuer": "mytenant.onmicrosoft.com",
      "issuerAssignedId": "johndoe19287456@gmail.com"
    }
  ],
  "passwordProfile" : {
    "password": "Soleil!23",
    "forceChangePasswordNextSignIn": false
  },
  "passwordPolicies": "DisablePasswordExpiration"
}

这允许用户使用电子邮件 (johndoe19287456@gmail.com) 或 ID (606198) 进行连接.

当用户输入他的ID,然后点击“忘记密码?” link,我想从 AAD 获取电子邮件值,这样用户就无法输入他想要的任何内容。但我仍然希望通过向该电子邮件地址发送代码来“验证”它。我有 2 个问题:

这是我尝试过的许多事情之一的示例。

构建块自定义声明:

      <ClaimType Id="ReadOnlyEmail">
        <DisplayName>Verified Email Address</DisplayName>
        <DataType>string</DataType>
        <UserInputType>Readonly</UserInputType>
      </ClaimType>
      <ClaimType Id="emailFromAAD">
        <DisplayName>Email from AAD</DisplayName>
        <DataType>string</DataType>
        <UserHelpText />
        <UserInputType>Readonly</UserInputType>
      </ClaimType>
      <ClaimType Id="readOnlySignInName">
        <DisplayName>Sign in name</DisplayName>
        <DataType>string</DataType>
        <UserHelpText />
        <UserInputType>Readonly</UserInputType>
      </ClaimType>
      <ClaimType Id="emailValue">
        <DisplayName>Matched mail</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="isEmailBoolean">
        <DisplayName>is Email</DisplayName>
        <DataType>boolean</DataType>
      </ClaimType>
      <ClaimType Id="strongAuthenticationEmailAddress">
        <DisplayName>string</DisplayName>
        <DataType>string</DataType>
        <AdminHelpText>Email address that the user can use for strong authentication.</AdminHelpText>
        <UserHelpText>Email address to use for strong authentication.</UserHelpText>
        <UserInputType>TextBox</UserInputType>
      </ClaimType>

声明转换:

<ClaimsTransformation Id="CopySignInNameFromReadOnly" TransformationMethod="FormatStringClaim">
        <InputClaims>
          <InputClaim ClaimTypeReferenceId="readOnlySignInName" TransformationClaimType="inputClaim" />
        </InputClaims>
        <InputParameters>
          <InputParameter Id="stringFormat" DataType="string" Value="{0}" />
        </InputParameters>
        <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="signInName" TransformationClaimType="outputClaim" />
        </OutputClaims>
      </ClaimsTransformation>

      <!-- If signin name match the regex, it is an email identifier. Oterwise, it'll be considered as username -->
      <ClaimsTransformation Id="isEmail" TransformationMethod="setClaimsIfRegexMatch">
        <InputClaims>
          <InputClaim ClaimTypeReferenceId="readOnlySignInName" TransformationClaimType="claimToMatch" />
        </InputClaims>
        <InputParameters>
          <InputParameter Id="matchTo" DataType="string" Value="[^@]+@[^\.]+\..+" />
          <InputParameter Id="outputClaimIfMatched" DataType="string" Value="isEmail" />
        </InputParameters>
        <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="emailValue" TransformationClaimType="outputClaim" />
          <OutputClaim ClaimTypeReferenceId="isEmailBoolean" TransformationClaimType="regexCompareResultClaim" />
        </OutputClaims>
      </ClaimsTransformation>

技术简介:

<!-- Password reset step 1b - Included in step SelfAsserted-LocalAccountLookup-Combined-PwdReset 
        That's where the input claim is define. signInName = the Username field on the screen -->
        <TechnicalProfile Id="SelfAsserted-LocalAccountLookup-Combined-SignUp">
          <DisplayName>Local Account Sign Up</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>
            <Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
          </Metadata>
          <IncludeInSso>false</IncludeInSso>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="readOnlySignInName" DefaultValue="{OIDC:LoginHint}" AlwaysUseDefaultValue="true" />
          </InputClaims>
          <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
            <OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
          </OutputClaims>
          <OutputClaimsTransformations>
            <OutputClaimsTransformation ReferenceId="CopySignInNameFromReadOnly" />
          </OutputClaimsTransformations>
          <ValidationTechnicalProfiles>
            <ValidationTechnicalProfile ReferenceId="regexAnalysisUsername" />
          </ValidationTechnicalProfiles>
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
        </TechnicalProfile>

<!-- Password reset step 1a. Includes SelfAsserted-LocalAccountLookup-Combined-SignUp 
        Input claim is defined in this. Here, we define output claims -->
        <TechnicalProfile Id="SelfAsserted-LocalAccountLookup-Combined-PwdReset">
          <DisplayName>Reset password</DisplayName>
          <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          <Metadata>
            <Item Key="setting.showCancelButton">false</Item>
            <Item Key="IpAddressClaimReferenceId">IpAddress</Item>
            <Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
            <Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
          </Metadata>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
            <OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
            <OutputClaim ClaimTypeReferenceId="objectId" />
            <OutputClaim ClaimTypeReferenceId="emailFromAAD" />
          </OutputClaims>
          <ValidationTechnicalProfiles>
            <ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingIdentifier" />
            <ValidationTechnicalProfile ReferenceId="regexAnalysisUsername" />
          </ValidationTechnicalProfiles>
          <IncludeTechnicalProfile ReferenceId="SelfAsserted-LocalAccountLookup-Combined-SignUp" />
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
        </TechnicalProfile>

        <!-- Password reset step 1c. Verify signin name on the "Continue" button clicked in the first screen -->
        <TechnicalProfile Id="AAD-UserReadUsingIdentifier">
          <Metadata>
            <Item Key="Operation">Read</Item>
            <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
          </Metadata>
          <IncludeInSso>false</IncludeInSso>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="readOnlySignInName" PartnerClaimType="signInNames" Required="true" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="objectId" />
            <OutputClaim ClaimTypeReferenceId="emailFromAAD" PartnerClaimType="signInNames.emailAddress" />
            <OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
          </OutputClaims>
          <IncludeTechnicalProfile ReferenceId="AAD-Common" />
        </TechnicalProfile>

<!-- Passwod reset step 2. Only if sign in data was a username -->
        <TechnicalProfile Id="LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress">
          <DisplayName>Reset password using username</DisplayName>
          <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          <Metadata>
            <!-- Other values maybe defined in localized api.selfasserted.fr and api.selfasserted.en -->
            <Item Key="IpAddressClaimReferenceId">IpAddress</Item>
            <Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
            <Item Key="AllowGenerationOfClaimsWithNullValues">true</Item>
            <Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">An account could not be found for the provided User ID and email combination.</Item>
            <Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
            <Item Key="LocalAccountType">Username</Item>
            <Item Key="LocalAccountProfile">true</Item>
            <!-- Reduce the default self-asserted retry limit of 7 for the reset journey -->
            <Item Key="setting.retryLimit">5</Item>
          </Metadata>
          <CryptographicKeys>
            <Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
          </CryptographicKeys>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="readOnlySignInName" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
            <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" DefaultValue="strongAuthenticationEmailAddress" Required="true" />
            <OutputClaim ClaimTypeReferenceId="authenticationSource" />
            <OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
            <OutputClaim ClaimTypeReferenceId="emailFromAAD" />
          </OutputClaims>
          <ValidationTechnicalProfiles>
            <ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingUserNameAndValidateStrongAuthenticationEmailAddress" />
          </ValidationTechnicalProfiles>
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
        </TechnicalProfile>

现在,我得到了第一个带有只读 ID 的屏幕。

下一个屏幕显示电子邮件的文本框。来自 AAD 的电子邮件不应该可见。

但无论如何它是空的,表明它没有从 AAD 得到任何东西,或者我没有将它正确地存放在领取袋中或传递下去。请注意,我已尝试根据 Microsoft 文档从“strongAuthenticationEmailAddress”和“signInNames.emailAddress”获取值,但它的 none 有效。也许这是我在 AAD-UserReadUsingIdentifier 配置文件中使用 PartnerClaimType 定义输出声明的方式?

为了给大家说清楚,下面是我想要的。一个简单的页面,包含 2 个只包含一个按钮的 reaonly 字段,用于发送代码,然后另一个用于在验证代码后继续。

谁能帮我解决这个问题?

我从 B2C 自定义策略启动包开始,并从 this community repo 添加自定义。

更新 这是我在查询用户时从 MS Graph 获得的信息:

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(identities,id,displayName,mail,surname,userPrincipalName,extension_06a19ccd80a0430c9730c62e4d96c895_ClientID,extension_06a19ccd80a0430c9730c62e4d96c895_requiresMigration)/$entity",
    "id": "051e***************",
    "displayName": "test - Tests2",
    "mail": "johndoe19287456@gmail.com",
    "surname": null,
    "userPrincipalName": "051ea****************@mytenant.onmicrosoft.com",
    "identities": [
        {
            "signInType": "emailAddress",
            "issuer": "mytenant.onmicrosoft.com",
            "issuerAssignedId": "johndoe19287456@gmail.com"
        },
        {
            "signInType": "userName",
            "issuer": "mytenant.onmicrosoft.com",
            "issuerAssignedId": "606198"
        },
        {
            "signInType": "userPrincipalName",
            "issuer": "mytenant.onmicrosoft.com",
            "issuerAssignedId": "051ea****************@mytenant.onmicrosoft.com"
        }
    ]
}

您必须在 LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress 中提供 emailFromAAD 作为输入声明,以便在此处预先填充。

https://docs.microsoft.com/en-us/azure/active-directory-b2c/self-asserted-technical-profile#input-claims

          <InputClaims>
            <InputClaim ClaimTypeReferenceId="readOnlySignInName" PartnerClaimType="signInNames" Required="true" />
            <InputClaim ClaimTypeReferenceId="emailFromAAD" />
          </InputClaims>

现在它将预填充到此页面上。

如果您想强制验证此电子邮件,请使用声明转换将其复制到只读声明中,然后执行:

<OutputClaim ClaimTypeReferenceId="readOnlyEmailFromAAD" PartnerClaimType="Verified.Email" Required="true" />

我按照 Jas Suri - MSFT 的建议以及其他一些事情设法从 AAD 获得了电子邮件地址。因此,总结一下这就是我所做的。我修改了验证 TP AAD-UserReadUsingIdentifier,只需将输出声明 emailFromAAD 替换为 signInNames.emailAddress(不再是 PartnerClaimType)。在 LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress TP 中,我添加了使用 CopyClaim 输入声明转换填充的 InputClaim emailFromAAD。我也清理了旅途的第一个TPSelfAsserted-LocalAccountLookup-Combined-PwdReset :

<ClaimsTransformation Id="CopySignInEmailAddressToEmail" TransformationMethod="CopyClaim">
    <InputClaims>
      <InputClaim ClaimTypeReferenceId="signInNames.emailAddress" TransformationClaimType="inputClaim" />
    </InputClaims>
    <OutputClaims>
      <OutputClaim ClaimTypeReferenceId="emailFromAAD" TransformationClaimType="outputClaim" />
    </OutputClaims>
</ClaimsTransformation>

<TechnicalProfile Id="SelfAsserted-LocalAccountLookup-Combined-PwdReset">
          <DisplayName>Reset password</DisplayName>
          <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          <Metadata>
            <Item Key="setting.showCancelButton">false</Item>
            <Item Key="IpAddressClaimReferenceId">IpAddress</Item>
            <Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
            <Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
          </Metadata>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
            <OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
            <OutputClaim ClaimTypeReferenceId="objectId" />
            <OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
            <OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
          </OutputClaims>
          <ValidationTechnicalProfiles>
            <ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingIdentifier" />
            <ValidationTechnicalProfile ReferenceId="regexAnalysisReadOnlyUsername" />
          </ValidationTechnicalProfiles>
          <IncludeTechnicalProfile ReferenceId="SelfAsserted-LocalAccountLookup-Combined-SignUp" />
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
        </TechnicalProfile>

<TechnicalProfile Id="AAD-UserReadUsingIdentifier">
          <Metadata>
            <Item Key="Operation">Read</Item>
            <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
          </Metadata>
          <IncludeInSso>false</IncludeInSso>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="readOnlySignInName" PartnerClaimType="signInNames" Required="true" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="objectId" />
            <OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
            <OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
          </OutputClaims>
          <IncludeTechnicalProfile ReferenceId="AAD-Common" />
        </TechnicalProfile>
      
<TechnicalProfile Id="LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress">
      <DisplayName>Reset password using username</DisplayName>
      <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      <Metadata>
        <!-- Other values maybe defined in localized api.selfasserted.fr and api.selfasserted.en -->
        <Item Key="IpAddressClaimReferenceId">IpAddress</Item>
        <Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
        <Item Key="AllowGenerationOfClaimsWithNullValues">true</Item>
        <Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">An account could not be found for the provided User ID and email combination.</Item>
        <Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
        <Item Key="LocalAccountType">Username</Item>
        <Item Key="LocalAccountProfile">true</Item>
        <!-- Reduce the default self-asserted retry limit of 7 for the reset journey -->
        <Item Key="setting.retryLimit">5</Item>
      </Metadata>
      <CryptographicKeys>
        <Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
      </CryptographicKeys>
      <InputClaimsTransformations>
        <InputClaimsTransformation ReferenceId="CopySignInEmailAddressToEmail" />
      </InputClaimsTransformations>
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="readOnlySignInName" />
        <InputClaim ClaimTypeReferenceId="emailFromAAD" />
      </InputClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
        <OutputClaim ClaimTypeReferenceId="emailFromAAD" PartnerClaimType="Verified.Email" Required="true" />
        <OutputClaim ClaimTypeReferenceId="authenticationSource" />
      </OutputClaims>
      <ValidationTechnicalProfiles>
        <ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingUserNameAndValidateStrongAuthenticationEmailAddress" />
      </ValidationTechnicalProfiles>
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
    </TechnicalProfile>