在 Azure AD B2C 中,如何在首次从社交登录登录时将社交帐户与任何现有本地帐户关联?

时间:2021-06-29 09:35:48

标签: azure-active-directory azure-ad-b2c azure-ad-graph-api

现在使用社交帐户 Facebook 登录时,我想检查是否已经有本地帐户 - 使用自定义策略。如果是这样,用户必须登录该帐户才能将其链接到社交帐户,否则会中止社交登录。

如果没有本地帐户,则只需创建社交帐户和具有随机密码的本地帐户,如here 所述。

已经有一个与 here 非常相似的 stackoverflow 问题,但它总是需要登录我不想要的本地帐户,并谈论“SQL 身份服务”,这对我来说毫无意义。< /p>

2 个答案:

答案 0 :(得分:0)

假设您使用电子邮件地址作为用户名,则每次登录该社交提供商时都需要获取该电子邮件地址。当您完成外部提供商交换(无论是 Facebook)时,您需要获取收到的电子邮件地址并使用允许通过电子邮件阅读目录的技术配置文件。有一个配置文件可以执行此操作,但是在找不到帐户时会引发错误,因此您需要一个稍微修改的版本,即将 RaiseErrorIfClaimsPrincipalDoesNotExist 键设置为 false:

  <TechnicalProfile Id="AAD-UserReadUsingEmailAddress-NoError">
    <Metadata>
        <Item Key="Operation">Read</Item>
        <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
        <Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided user ID.</Item>
    </Metadata>
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="email" PartnerClaimType="logonIdentifier" Required="true" />
    </InputClaims>
    <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="objectId" />
        <OutputClaim ClaimTypeReferenceId="accountEnabled" />
        <OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
    </OutputClaims>
    <OutputClaimsTransformations>
        <OutputClaimsTransformation ReferenceId="AssertAccountEnabledIsTrue" />
        <OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromObjectID" />
    </OutputClaimsTransformations>
    <IncludeTechnicalProfile ReferenceId="AAD-ReadCommon" />
  </TechnicalProfile>

执行配置文件后,您将通过检查 ObjectId 知道是否找到了用户并采取相应措施。

答案 1 :(得分:0)

我使用 wojtekdo 的 TechnicalProfile 作为起点,并仔细阅读了自定义配置文件中的 MS documents 并着眼于我想要做的事情,设法使这项工作得以实现。我还从 Jas Suri 评论中提供的 link 中获取了 userIdentities 的概念。

我使用子旅程将社交帐户与本地帐户合并,只是为了将其与主要旅程分开。

请注意,我不会验证或询问本地帐户的密码。我认为这是可以接受的,因为用户在创建 facebook 帐户时已经验证了电子邮件的所有权。不过,我更愿意验证本地帐户,并且我有关于如何执行此操作的 Stackoverflow question

TrustFrameworkExtensions xml 来实现:

<BuildingBlocks>
  <ClaimsSchema>
    <ClaimType Id="userIdentity">
      <DisplayName>userIdentity</DisplayName>
      <DataType>userIdentity</DataType>
      <AdminHelpText>userIdentity</AdminHelpText>
      <UserHelpText>userIdentity</UserHelpText>
    </ClaimType>
    <ClaimType Id="userIdentities">
      <DisplayName>userIdentities</DisplayName>
      <DataType>userIdentityCollection</DataType>
      <AdminHelpText>userIdentities</AdminHelpText>
      <UserHelpText>userIdentities</UserHelpText>
    </ClaimType>
    <ClaimType Id="issuers">
      <DisplayName>issuers</DisplayName>
      <DataType>stringCollection</DataType>
      <UserHelpText>User identity providers. This information is received from alternativeSecurityIds</UserHelpText>
    </ClaimType>
    <ClaimType Id="signInNamesInfo.emailAddress">
      <DisplayName>Email Address</DisplayName>
      <DataType>string</DataType>
      <AdminHelpText>Email address that the user can use to sign in.</AdminHelpText>
      <UserHelpText>Email address to use for signing in.</UserHelpText>
      <UserInputType>TextBox</UserInputType>
    </ClaimType>
    <ClaimType Id="emails">
      <DisplayName>Email Addresses</DisplayName>
      <DataType>stringCollection</DataType>
      <AdminHelpText>Email addresses of the user.</AdminHelpText>
      <UserHelpText>Your email addresses.</UserHelpText>
    </ClaimType>
    <ClaimType Id="strongAuthenticationEmailAddress">
      <DisplayName>Email Address</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>
  </ClaimsSchema>

  <ClaimsTransformations>
    <ClaimsTransformation Id="CreateEmailsFromOtherMailsAndSignInNamesInfo" TransformationMethod="AddItemToStringCollection">
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="signInNamesInfo.emailAddress" TransformationClaimType="item" />
        <InputClaim ClaimTypeReferenceId="otherMails" TransformationClaimType="collection" />
      </InputClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="emails" TransformationClaimType="collection" />
      </OutputClaims>
    </ClaimsTransformation>
    <ClaimsTransformation Id="AddStrongAuthenticationEmailToEmails" TransformationMethod="AddItemToStringCollection">
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" TransformationClaimType="item" />
        <InputClaim ClaimTypeReferenceId="emails" TransformationClaimType="collection" />
      </InputClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="emails" TransformationClaimType="collection" />
      </OutputClaims>
    </ClaimsTransformation>
    <ClaimsTransformation Id="CreateSubjectClaimFromObjectID" TransformationMethod="CreateStringClaim">
      <InputParameters>
        <InputParameter Id="value" DataType="string" Value="Not supported currently. Use oid claim." />
      </InputParameters>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="sub" TransformationClaimType="createdClaim" />
      </OutputClaims>
    </ClaimsTransformation>
    <!-- Sample: On sign-in (first time) with social account, create a userIdentity claim, using issuerUserId and issuer name -->
    <ClaimsTransformation Id="CreateUserIdentity" TransformationMethod="CreateUserIdentity">
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="issuerUserId" TransformationClaimType="issuerUserId" />
        <InputClaim ClaimTypeReferenceId="identityProvider" TransformationClaimType="issuer" />
      </InputClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="userIdentity" TransformationClaimType="userIdentity" />
      </OutputClaims>
    </ClaimsTransformation>
    
    <!--Sample: Add a userIdentity to the userIdentities collection. .-->
    <ClaimsTransformation Id="AppendUserIdentity" TransformationMethod="AddItemToUserIdentityCollection">
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="userIdentity" TransformationClaimType="item" />
        <InputClaim ClaimTypeReferenceId="userIdentities" TransformationClaimType="collection" />
      </InputClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="userIdentities" TransformationClaimType="collection" />
      </OutputClaims>
    </ClaimsTransformation>

    <!--Sample: Extracts the list of social identity providers associated with the user -->
    <ClaimsTransformation Id="ExtractIssuers" TransformationMethod="GetIssuersFromUserIdentityCollectionTransformation">
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="userIdentities" TransformationClaimType="userIdentityCollection" />
      </InputClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="issuers" TransformationClaimType="issuersCollection" />
      </OutputClaims>
    </ClaimsTransformation>
  </ClaimsTransformations>
</BuildingBlocks>

<ClaimsProviders>
  <ClaimsProvider>
  <DisplayName>Azure Active Directory</DisplayName>
  <TechnicalProfiles>
    <TechnicalProfile Id="AAD-ReadCommon">
      <Metadata>
        <Item Key="Operation">Read</Item>
        <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
      </Metadata>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="userPrincipalName" />
        <OutputClaim ClaimTypeReferenceId="displayName" />
        <OutputClaim ClaimTypeReferenceId="otherMails" />
        <OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" PartnerClaimType="signInNames.emailAddress" />
      </OutputClaims>
      <OutputClaimsTransformations>
        <OutputClaimsTransformation ReferenceId="CreateEmailsFromOtherMailsAndSignInNamesInfo" />
        <OutputClaimsTransformation ReferenceId="AddStrongAuthenticationEmailToEmails" />
      </OutputClaimsTransformations>
      <IncludeTechnicalProfile ReferenceId="AAD-Common" />
    </TechnicalProfile>

    <TechnicalProfile Id="AAD-UserReadUsingEmailAddress-NoError">
      <Metadata>
        <Item Key="Operation">Read</Item>
        <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
        <Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided user ID.</Item>
      </Metadata>
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="email" PartnerClaimType="logonIdentifier" Required="true" />
      </InputClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="objectId" />
        <OutputClaim ClaimTypeReferenceId="accountEnabled" />
      </OutputClaims>
      <IncludeTechnicalProfile ReferenceId="AAD-ReadCommon" />
    </TechnicalProfile>

    <TechnicalProfile Id="AAD-AssertAccountEnabledAndCreateSubjectClaimFromObjectId">
      <DisplayName>Assert account enabled</DisplayName>
      <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" Required="true" />
      </InputClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="objectId" />
        <OutputClaim ClaimTypeReferenceId="accountEnabled" />
        <OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
        <OutputClaim ClaimTypeReferenceId="email" />
      </OutputClaims>
      <OutputClaimsTransformations>
        <OutputClaimsTransformation ReferenceId="AssertAccountEnabledIsTrue" />
        <OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromObjectID" />
      </OutputClaimsTransformations>
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
    </TechnicalProfile>

    <TechnicalProfile Id="AAD-UserUpdateWithUserIdentities">
      <Metadata>
        <Item Key="api-version">1.6</Item>
        <Item Key="Operation">Write</Item>
        <Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">false</Item>
        <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
      </Metadata>
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="objectId" Required="true" />
      </InputClaims>
      <PersistedClaims>
        <PersistedClaim ClaimTypeReferenceId="objectId" />
        <PersistedClaim ClaimTypeReferenceId="userIdentities" />
        <!--<PersistedClaim ClaimTypeReferenceId="extension_requiresMigrationBool" DefaultValue="false" AlwaysUseDefaultValue="true"/>-->
      </PersistedClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="objectId" />
        <OutputClaim ClaimTypeReferenceId="userIdentities" />
      </OutputClaims>
      <OutputClaimsTransformations>
        <OutputClaimsTransformation ReferenceId="ExtractIssuers" />
      </OutputClaimsTransformations>
      <IncludeTechnicalProfile ReferenceId="AAD-Common" />
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
    </TechnicalProfile>
    </TechnicalProfiles>
  </ClaimsProvider>

  <ClaimsProvider>
    <DisplayName>Facebook</DisplayName>
    <TechnicalProfiles>
      <TechnicalProfile Id="Facebook-OAUTH">
        <OutputClaimsTransformations>
          <OutputClaimsTransformation ReferenceId="CreateUserIdentity" />
          <OutputClaimsTransformation ReferenceId="AppendUserIdentity" />
        </OutputClaimsTransformations>
      </TechnicalProfile>
    </TechnicalProfiles>
  </ClaimsProvider>
</ClaimsProviders>

<UserJourneys>
  <UserJourney Id="SignUpOrSignIn">
    <OrchestrationSteps>

      <!-- For social IDP authentication, attempt to find the user account in the directory. -->
      <OrchestrationStep Order="4" Type="ClaimsExchange">
        <Preconditions>
          <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
            <Value>authenticationSource</Value>
            <Value>localAccountAuthentication</Value>
            <Action>SkipThisOrchestrationStep</Action>
          </Precondition>
        </Preconditions>
        <ClaimsExchanges>
          <ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError"/>
        </ClaimsExchanges>
      </OrchestrationStep>

      <!-- Find local account using email-->        
      <OrchestrationStep Order="5" Type="ClaimsExchange">
        <Preconditions>
          <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
            <Value>authenticationSource</Value>
            <Value>localAccountAuthentication</Value>
            <Action>SkipThisOrchestrationStep</Action>
          </Precondition>
        </Preconditions>
        <ClaimsExchanges>
          <ClaimsExchange Id="FindLocalAccount" TechnicalProfileReferenceId="AAD-UserReadUsingEmailAddress-NoError"/>
        </ClaimsExchanges>
      </OrchestrationStep>

      <!-- start a subjourney to verify local account if one was found in previous step -->
      <OrchestrationStep Order="6" Type="InvokeSubJourney">
        <Preconditions>
          <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
            <Value>authenticationSource</Value>
            <Value>localAccountAuthentication</Value>
            <Action>SkipThisOrchestrationStep</Action>
          </Precondition>
          <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
            <Value>objectId</Value>
            <Action>SkipThisOrchestrationStep</Action>
          </Precondition>
        </Preconditions>
        <JourneyList>
          <Candidate SubJourneyReferenceId="MergeWithLocalAccount" />
        </JourneyList>
      </OrchestrationStep>

    </OrchestrationSteps>
  </UserJourney>
</UserJourneys>

<SubJourneys>
  <SubJourney Id="MergeWithLocalAccount" Type="Call">
    <OrchestrationSteps>
      <!-- assert any found local account is enabled -->
      <OrchestrationStep Order="1" Type="ClaimsExchange">
        <Preconditions>
          <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
            <Value>authenticationSource</Value>
            <Value>localAccountAuthentication</Value>
            <Action>SkipThisOrchestrationStep</Action>
          </Precondition>
          <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
            <Value>objectId</Value>
            <Action>SkipThisOrchestrationStep</Action>
          </Precondition>
        </Preconditions>
        <ClaimsExchanges>
          <ClaimsExchange Id="AssertLocalAccountEnabled" TechnicalProfileReferenceId="AAD-AssertAccountEnabledAndCreateSubjectClaimFromObjectId"/>
        </ClaimsExchanges>
      </OrchestrationStep>

      <!-- merge account with any existing and verified local account-->
      <OrchestrationStep Order="2" Type="ClaimsExchange">
        <Preconditions>
          <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
            <Value>authenticationSource</Value>
            <Value>localAccountAuthentication</Value>
            <Action>SkipThisOrchestrationStep</Action>
          </Precondition>
          <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
            <Value>objectId</Value>
            <Action>SkipThisOrchestrationStep</Action>
          </Precondition>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
            <Value>accountVerified</Value>
            <Action>SkipThisOrchestrationStep</Action>
          </Precondition>
        </Preconditions>
        <ClaimsExchanges>
          <ClaimsExchange Id="AADMergeAccount" TechnicalProfileReferenceId="AAD-UserUpdateWithUserIdentities" />
        </ClaimsExchanges>
      </OrchestrationStep>
    </OrchestrationSteps>
  </SubJourney>
</SubJourneys>

我已经包含了为解决合并社交和本地帐户而添加的所有 xml,不包括其他所有内容。我知道这不是一个最小的解决方案,有些部分可能不需要,但它是一个可行的解决方案,可能会帮助其他人解决这个问题。