Using identities and linking them in Azure AD B2C

There’s a good description of identities in Azure AD B2C here.
To paraphrase:
“A customer account, which could be a consumer, partner, or citizen, can be associated with these identity types:
- Local identity — The username and password are stored locally in the Azure AD B2C directory. Theses are called local accounts.
- Federated identity — Also known as a social or enterprise accounts, the identity of the user is managed by a federated identity provider like Facebook or ADFS.
A user with a customer account can sign in with multiple identities. For example, username, email, employee ID, government ID, and others. A single account can have multiple identities, both local and social, with the same password.
In the Microsoft Graph API, both local and federated identities are stored in the user identities
attribute, which is of type objectIdentity.
The identities
collection represents a set of identities used to sign in to a user account. This collection enables the user to sign in to the user account with any of its associated identities.
The identities attribute can contain up to ten objectIdentity objects.
Each object contains the following properties:
signInType
Specifies the user sign-in types in your directory.
For local account: emailAddress
, emailAddress1
, emailAddress2
, emailAddress3
, userName
, or any other type you like.
Social accounts must be set to federated
. issuer string.
This specifies the issuer of the identity. For local accounts (where signInType is not federated
), this property is the local B2C tenant default domain name, for example contoso.onmicrosoft.com
.
For social identity (where signInType is federated
) the value is the name of the issuer, for example facebook.com
issuerAssignedId
Specifies the unique identifier assigned to the user by the issuer. The combination of issuer and issuerAssignedId must be unique within your tenant.
For local accounts, when signInType is set to emailAddress
or userName
, it represents the sign-in name for the user.
When signInType is set to:
emailAddress
(or starts withemailAddress
likeemailAddress1
) issuerAssignedId must be a valid email addressuserName
(or any other value), issuerAssignedId must be a valid local part of an email addressfederated
, issuerAssignedId represents the federated account unique identifier
e.g. a local account identity with a sign-in name, an email address as sign-in, and with a social identity.
"identities": [
{
"signInType": "userName",
"issuer": "contoso.onmicrosoft.com",
"issuerAssignedId": "johnsmith"
},
{
"signInType": "emailAddress",
"issuer": "contoso.onmicrosoft.com",
"issuerAssignedId": "jsmith@yahoo.com"
},
{
"signInType": "federated",
"issuer": "facebook.com",
"issuerAssignedId": "5eecb0cd"
}
]
“ (End of quote).
In this example, the user could sign in with “johnsmith” or “jsmith@yahoo.com”. They would share a common password.
Custom Policy
In a custom policy, the user above would use:
“SelfAsserted-LocalAccountSignin-Email” or
“SelfAsserted-LocalAccountSignin-Username”
For the Facebook account, the user would sign in using Facebook. This may have a different sign in name and password.
Local accounts
The “signinname” can be anything you like. In the policies samples, you will find:
- “signInNames.emailAddress”
- “signInNames.username”
- “signInNames.phoneNumber”
- “signInNames.oidToLink”
For the phoneNumber sample e.g.
<TechnicalProfile Id="CombineCountryCodeAndNationalNumber">
<DisplayName>Combine country code and national number</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="ConvertStringToPhoneNumber"/>
</InputClaimsTransformations>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInNames.phoneNumber"/>
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop"/>
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserReadUsingPhoneNumber">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInNames.phoneNumber" Required="true"/>
</InputClaims>...
So the phoneNumber is a combination of country code and national number resulting in “signInNames.phoneNumber”.
Notice that the input to “AAD-UserReadUsingPhoneNumber” is “signInNames.phoneNumber” whereas the input to “AAD-UserReadUsingUserName” would be “signInNames.username”.
If the users have a linked loyaltyNumber, then you could have:
- “signInNames.username”
- “signInNames.loyaltyNumber”
so you could sign in either with the username or the loyaltyNumber.
Federated accounts
At the moment, there is a problem.
In the base file, we find:
<ClaimsTransformation Id="CreateAlternativeSecurityId" TransformationMethod="CreateAlternativeSecurityId">
<InputClaims>
<InputClaim ClaimTypeReferenceId="issuerUserId" TransformationClaimType="key"/>
<InputClaim ClaimTypeReferenceId="identityProvider" TransformationClaimType="identityProvider"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="alternativeSecurityId" TransformationClaimType="alternativeSecurityId"/>
</OutputClaims>
</ClaimsTransformation>
and the documentation is here.
These are always of type “federated”.
Using the Facebook example above:
- identityProvider = facebook.com
- issuerUserId = 5eecb0cd
The collection is called “AlternativeSecurityIds”.
You can use “AddItemToAlternativeSecurityIdCollection” to build up a collection of these.
However, in the custom policy samples for account linking, we find:
<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>
A userIdentity is the equivalent of alternativesecurityId and the collection is called userIdentities.
You can use “AddItemToUserIdentityCollection” to build up a collection of these.
There is currently no documentation around this.
They are essentially two different naming conventions that refer to the same base structure.
I’ve added a gist that shows this in action.
The steps are:
<OutputClaimsTransformation ReferenceId="CreateIssuer"/>
<OutputClaimsTransformation ReferenceId="CreateIssuerUserId"/>
<OutputClaimsTransformation ReferenceId="CreateUserIdentityToLink"/>
<OutputClaimsTransformation ReferenceId="AppendUserIdentityToLink"/>
Using the Facebook example, the:
- identityProvider = facebook.com
- issuerId = 5eecb0cd
You combine these to create a userIdentity and then add that to the userIdentities collection.
You then link the federated identity to the local user by using “AAD-UserUpdateWithUserIdentities” and adding the userIdentities collection.
...
PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId"/>
<PersistedClaim ClaimTypeReferenceId="userIdentities"/>
</PersistedClaims>
...
Note that while you can add a federated account to a local account, the reverse is not true. “This is due to not being able to add a Local Account to a Federated-only account.”
Using custom policies, the “signInType” is always “federated”.
Non-federated accounts
To have “different” signin types, you have to use something like:
- “signInNames.username”
- “signInNames.loyalty”
It is not possible to have two “signInNames.username” with different values as the one overwrites the other.
The basic problem is that there doesn’t seem to be a collection of signInNames.
The custom policy below is also in the gist above.
Let’s link a loyalty number to an existing user name.
We ask for the two pieces of information, then we read by username to get the objectId and then we write by objectId with:
<PersistedClaim ClaimTypeReferenceId="username" PartnerClaimType="signInNames.userName"/>
<PersistedClaim ClaimTypeReferenceId="loyaltyName" PartnerClaimType="signInNames.loyalty"/>
This links the two together.
Run the policy and type an existing user name and a loyalty number.

This will link the two. You see the result in the JWT returned.

Looking at the linking in the portal (see Portal below), you see that they are linked.

If I try and run the policy again with a different loyalty number, the loyalty number I type in overwrites the loyalty number above.
There is, however, a problem with this.
To read the user using username, you need “AAD-UserReadUsingUsername” where the input is “signInNames.username”.
To read the user using loyalty number, you need to create a “AAD-UserReadUsingLoyaltyNumber” where the input is “signInNames.loyaltyNumber”.
The problem is when to use each one. You could say that loyalty numbers are all digits and usernames have to have a least one non-digit character and then use a regex to decide but that’s not ideal.
Luckily, there’s a neat trick to get around this 😃
The custom policy for this is also in the gist above.
It’s based on this sample. Thanks to @Jas for pointing me in the right direction.
<TechnicalProfile Id="AAD-UserReadUsingIdentifier">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="signInNames" Required="true"/>
</InputClaims>...
The idea is that you put the identifier in “signInName” and use “signInNames” as the partner.

Running the sample, you need to enter the signInName. I can enter either “linkuser” or “12340987” and I get the same claims for both! I don’t need to figure out which is which.


Very neat!
The identities need to be unique. If I pick another user and try and link that identity to “12340987” (that has already been linked above), I get:

Graph API
The API to create users and identities is here. There’s a sample here. Look at “Example 2” for an outline of how to create the “identities” JSON array.
Note that when using the Graph API, you can specify the “signInType”. This allows you to have a local user with two different usernames.
You could use either username in “AAD-UserReadUsingUsername” and you would get the same claims returned.
You can also use this client and add the “identities” structure in the JSON.
Portal
You can create multiple identities for a user in the portal.

When you view the user, click on the hyperlink.

This shows you all the identities the user has.

The UPN is the base identity that all the others link to.
All good!