AAD B2C Custom Policy | How to use existing B2C local user for social sign-ins and return b2c extension attribute values in claims.

Kiran Zende 85 Reputation points
2023-06-08T12:50:15.5533333+00:00

Hi,

I am using Azure Ad B2C - Identity Experience Framework - custom policy to implement sign-in flow with multiple identity providers which is also referred as HRD (Home Realm Discovery).

In my scenario, users would already be present in AAD B2C as a B2C local User, with type="Member" and Source="Azure Active Directory".

They have extension attributes set with values. And I want to return these extension attribute values as claims on successful authentication.

Users are authenticated based on their domain, each domain have their own identity provider configured.

After successful authentication from external Idps, custom policy should read extension attributes from B2C and return in claims.

I am facing an issue here - new user is getting created with external identity provider identities even if user with same email exists. New user does not have any extension attribute values set which results in getting no data in claims.

For example :

Local B2C user with email "******@xyz.com" already exists, with type="Member" and Source="Azure Active Directory" (local B2C user config). Also have "extension_role" set to "Admin".

When ******@xyz.com tries to sign-in, HRD logic redirects user to XYZ identity provider login page. User is authenticated.

However, new user with XYZ identity gets created in B2C, with type="Member" and Source="Other". (external idp user config)

New user does not have any value set to "extension_role". I do not get this data in claims.

I want custom policy to use existing B2C local user for social sign-ins and return b2c extension attribute values in claims.

Is it feasible? Any suggestions on how can I achieve this?

Thanks in advance.

Microsoft Security | Microsoft Entra | Microsoft Entra External ID
{count} votes

1 answer

Sort by: Most helpful
  1. Akshay-MSFT 17,956 Reputation points Microsoft Employee Moderator
    2023-06-13T11:05:36.9733333+00:00

    @Kiran Zende

    Thank you for posting your query. From above description I could understand that you have policy to map an extension attribute value with each user signing up with their email address (local account). However if same user signup with social account (containing same email), then a duplicate account is getting created and is not returning any extension attribute (as it is not mapped with this account).

    To fix this we could use email uniqueness to stop user from signing up with social account if email is not exist.

    • We could define the following technical profile for local and social accounts which will be used during user journey to validate the email duplicity :
     <ClaimsProvider>
          <DisplayName>Social accounts</DisplayName>
          <TechnicalProfiles>
              <!-- Demo: Collect, validate and return the email address as output claim.
              So, next orchestration step will persist the email to the directory 
              into otherMails attribute -->
            <TechnicalProfile Id="SelfAsserted-SocialEmail">
              <DisplayName>Email signup</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.localaccountsignup</Item>
                <Item Key="language.button_continue">Continue</Item>
              </Metadata>
              <InputClaims>
                <InputClaim ClaimTypeReferenceId="email" />
              </InputClaims>
              <OutputClaims>
                <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
              </OutputClaims>
              <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
            </TechnicalProfile>
          </TechnicalProfiles>
        </ClaimsProvider>
        
        <ClaimsProvider>
          <DisplayName>Azure Active Directory</DisplayName>
            <TechnicalProfiles>
            <!--Demo: This technical profile tries to find a local account with provided email address-->
            <TechnicalProfile Id="AAD-UserReadUsingLocalAccountEmail-NoError">
              <Metadata>
                <Item Key="Operation">Read</Item>
                <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
              </Metadata>
              <InputClaims>
                <InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames" Required="true" />
              </InputClaims>
              <OutputClaims>
                <!-- Required claims -->
                <OutputClaim ClaimTypeReferenceId="tempObjectId" PartnerClaimType="objectId"/>
              </OutputClaims>
              <IncludeTechnicalProfile ReferenceId="AAD-Common" />
            </TechnicalProfile>
            
            <!--Demo: This technical profile tries to find a social account with provided email address-->
            <TechnicalProfile Id="AAD-UserReadUsingSocialEmail-NoError">
              <Metadata>
                <Item Key="Operation">Read</Item>
                <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
              </Metadata>
              <InputClaims>
                <InputClaim ClaimTypeReferenceId="email" PartnerClaimType="strongAuthenticationEmailAddress" Required="true" />
              </InputClaims>
              <OutputClaims>
                <!-- Required claims -->
                <OutputClaim ClaimTypeReferenceId="tempObjectId" PartnerClaimType="objectId"/>
              </OutputClaims>
              <IncludeTechnicalProfile ReferenceId="AAD-Common" />
            </TechnicalProfile>
            </TechnicalProfiles>
          </ClaimsProvider>
    
        <ClaimsProvider>
          <DisplayName>Self Asserted</DisplayName>
          <TechnicalProfiles>
            <!--Demo: this technical profile displays the message to the user-->
            <TechnicalProfile Id="SelfAsserted-UniqueUserMessage">
              <DisplayName>Email unique validation</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>
                <!--Demo: hide the continue and cancel buttons -->
                <Item Key="setting.showContinueButton">false</Item>
                <Item Key="setting.showCancelButton">false</Item>
              </Metadata>
              <InputClaimsTransformations>
                <InputClaimsTransformation ReferenceId="CreateTheUniqueEmailErrorMessage" />
              </InputClaimsTransformations>
              <InputClaims>
                 <InputClaim ClaimTypeReferenceId="userMessage" />
              </InputClaims>
              <OutputClaims>
                <!--Demo: Show the paragraph claim with the message to the user -->
                <OutputClaim ClaimTypeReferenceId="userMessage" />
              </OutputClaims>
              <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
            </TechnicalProfile>
          </TechnicalProfiles>
        </ClaimsProvider>
      </ClaimsProviders>
    
    • The User journey would have following steps to validate if user email address is already associated with any of the existing user accounts:
    <!--Demo: Run this step only if email address is empty and new user.
    			The technical profile asks the user to provide and validate the email address.
    			The email address return as output claim. So, next step can 
    			persist the email to the directory -->
            <OrchestrationStep Order="4" Type="ClaimsExchange">
              <Preconditions>
    		        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
                  <Value>objectId</Value>
                  <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
                <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
                  <Value>email</Value>
                  <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
              </Preconditions>		
              <ClaimsExchanges>
                <ClaimsExchange Id="SelfAsserted-SocialEmail" TechnicalProfileReferenceId="SelfAsserted-SocialEmail" />
              </ClaimsExchanges>
            </OrchestrationStep>
    		
            <!-- Demo:  Run this step only if email address is empty and new user.
    			    The technical profile checks if there is another local account with same email address.-->
            <OrchestrationStep Order="5" Type="ClaimsExchange">
              <Preconditions>
    		        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
                  <Value>objectId</Value>
                  <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
              </Preconditions>
              <ClaimsExchanges>
                <ClaimsExchange Id="AAD-UserReadUsingLocalAccountEmail-NoError" TechnicalProfileReferenceId="AAD-UserReadUsingLocalAccountEmail-NoError" />
              </ClaimsExchanges>
            </OrchestrationStep>
    
            <!-- Demo:  Run this step only if email address is empty and new user.
    			    The technical profile checks if there is another social account with same email address.-->
            <!-- <OrchestrationStep Order="6" Type="ClaimsExchange">
              <Preconditions>
                <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
                  <Value>objectId</Value>
                  <Action>SkipThisOrchestrationStep</Action>
                </Precondition>          
    		        <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
                  <Value>tempObjectId</Value>
                  <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
              </Preconditions>
              <ClaimsExchanges>
                <ClaimsExchange Id="AAD-UserReadUsingSocialEmail-NoError" TechnicalProfileReferenceId="AAD-UserReadUsingSocialEmail-NoError" />
              </ClaimsExchanges>
            </OrchestrationStep> -->
        
            <!-- Demo:  Run this step only if email address is empty and new user.
    			    The technical profile checks if there is another local account with same email address.-->
            <OrchestrationStep Order="6" Type="ClaimsExchange">
              <Preconditions>
                <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
                  <Value>objectId</Value>
                  <Action>SkipThisOrchestrationStep</Action>
                </Precondition>          
    		        <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
                  <Value>tempObjectId</Value>
                  <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
              </Preconditions>
              <ClaimsExchanges>
                <ClaimsExchange Id="SelfAsserted-UniqueUserMessage" TechnicalProfileReferenceId="SelfAsserted-UniqueUserMessage" />
              </ClaimsExchanges>
            </OrchestrationStep>
        
            <!-- Show self-asserted page only if the directory does not have the user account already (i.e. we do not have an objectId). 
              This can only happen when authentication happened using a social IDP. If local account was created or authentication done
              using ESTS in step 2, then an user account must exist in the directory by this time. -->
            <OrchestrationStep Order="7" Type="ClaimsExchange">
              <Preconditions>
                <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
                  <Value>objectId</Value>
                  <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
              </Preconditions>
              <ClaimsExchanges>
                <ClaimsExchange Id="SelfAsserted-Social" TechnicalProfileReferenceId="SelfAsserted-Social" />
              </ClaimsExchanges>
            </OrchestrationStep>
    
    
    

    Please do let me know if you have any further queries by responding in comments section.

    Thanks,

    Akshay Kaushik

    Please "Accept the answer" (Yes), and share your feedback if the suggestion answers you’re your query. This will help us and others in the community as well.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.