We're looking to query users in the azure ad b2c directory and programmatically extract their login email for all types of users. Which we understand to be:
when running the api call using the azure ad graph explorer:
https://graph.windows.net/myorganization/users?api-version=1.6
we can see all these users. (data is sanitized a bit)
Example Standard User
{
"odata.type": "Microsoft.DirectoryServices.User",
"objectType": "User",
"objectId": "8b7c468b-fec4-4ff2-b448-64f99f3fa9ff",
"deletionTimestamp": null,
"accountEnabled": true,
"assignedLicenses": [],
"assignedPlans": [],
"city": null,
"companyName": null,
"country": null,
"creationType": null,
"department": null,
"dirSyncEnabled": null,
"displayName": "Global User",
"employeeId": null,
"facsimileTelephoneNumber": null,
"givenName": null,
"immutableId": null,
"isCompromised": null,
"jobTitle": null,
"lastDirSyncTime": null,
"mail": null,
"mailNickname": "global.user",
"mobile": null,
"onPremisesDistinguishedName": null,
"onPremisesSecurityIdentifier": null,
"otherMails": [],
"passwordPolicies": null,
"passwordProfile": null,
"physicalDeliveryOfficeName": null,
"postalCode": null,
"preferredLanguage": null,
"provisionedPlans": [],
"provisioningErrors": [],
"proxyAddresses": [],
"refreshTokensValidFromDateTime": "2017-10-31T17:20:29Z",
"showInAddressList": null,
"signInNames": [],
"sipProxyAddress": null,
"state": null,
"streetAddress": null,
"surname": null,
"telephoneNumber": null,
"usageLocation": null,
"userIdentities": [],
"userPrincipalName": "[email protected]",
"userType": "Member"
}
Example Guest User
{
"odata.type": "Microsoft.DirectoryServices.User",
"objectType": "User",
"objectId": "6458e1fc-c27b-40cb-b83d-2124f0999130",
"deletionTimestamp": null,
"accountEnabled": true,
"assignedLicenses": [],
"assignedPlans": [],
"city": null,
"companyName": null,
"country": null,
"creationType": null,
"department": null,
"dirSyncEnabled": null,
"displayName": "displayname",
"employeeId": null,
"facsimileTelephoneNumber": null,
"givenName": "givenname",
"immutableId": null,
"isCompromised": null,
"jobTitle": null,
"lastDirSyncTime": null,
"mail": null,
"mailNickname": "qa_theaccesshub.com#EXT#",
"mobile": null,
"onPremisesDistinguishedName": null,
"onPremisesSecurityIdentifier": null,
"otherMails": [
"[email protected]"
],
"passwordPolicies": null,
"passwordProfile": null,
"physicalDeliveryOfficeName": null,
"postalCode": null,
"preferredLanguage": null,
"provisionedPlans": [],
"provisioningErrors": [],
"proxyAddresses": [],
"refreshTokensValidFromDateTime": "2017-10-31T15:36:22Z",
"showInAddressList": null,
"signInNames": [],
"sipProxyAddress": null,
"state": null,
"streetAddress": null,
"surname": "surname",
"telephoneNumber": null,
"usageLocation": null,
"userIdentities": [],
"userPrincipalName": "qa_theaccesshub.com#EXT#@qa2clientb2ctheaccesshub.onmicrosoft.com",
"userType": "Member"
}
Example Local User
{
"odata.type": "Microsoft.DirectoryServices.User",
"objectType": "User",
"objectId": "a941e75d-2c1b-4383-9d6c-783c1d008479",
"deletionTimestamp": null,
"accountEnabled": true,
"assignedLicenses": [],
"assignedPlans": [],
"city": null,
"companyName": null,
"country": null,
"creationType": "LocalAccount",
"department": null,
"dirSyncEnabled": null,
"displayName": "Display Name",
"employeeId": null,
"facsimileTelephoneNumber": null,
"givenName": "Glen",
"immutableId": null,
"isCompromised": null,
"jobTitle": null,
"lastDirSyncTime": null,
"mail": null,
"mailNickname": "98c4f2cf-a452-46a4-a33f-6fb451bc3f59",
"mobile": null,
"onPremisesDistinguishedName": null,
"onPremisesSecurityIdentifier": null,
"otherMails": [],
"passwordPolicies": "DisablePasswordExpiration",
"passwordProfile": null,
"physicalDeliveryOfficeName": null,
"postalCode": null,
"preferredLanguage": null,
"provisionedPlans": [],
"provisioningErrors": [],
"proxyAddresses": [],
"refreshTokensValidFromDateTime": "2017-11-03T18:18:36Z",
"showInAddressList": null,
"signInNames": [
{
"type": "emailAddress",
"value": "[email protected]"
}
],
"sipProxyAddress": null,
"state": null,
"streetAddress": null,
"surname": "Martin",
"telephoneNumber": null,
"usageLocation": null,
"userIdentities": [],
"userPrincipalName": "98c4f2cf-a452-46a4-a33f-6fb451bc3f59@qa2clientb2ctheaccesshub.onmicrosoft.com",
"userType": "Member"
}
Example Social User
{
"odata.type": "Microsoft.DirectoryServices.User",
"objectType": "User",
"objectId": "917bddd5-40d8-4a25-9a6e-8317a6949b48",
"deletionTimestamp": null,
"accountEnabled": false,
"assignedLicenses": [],
"assignedPlans": [],
"city": null,
"companyName": null,
"country": null,
"creationType": null,
"department": null,
"dirSyncEnabled": null,
"displayName": "Display Name",
"employeeId": null,
"facsimileTelephoneNumber": null,
"givenName": "GivenName",
"immutableId": null,
"isCompromised": null,
"jobTitle": null,
"lastDirSyncTime": null,
"mail": null,
"mailNickname": "unknown",
"mobile": null,
"onPremisesDistinguishedName": null,
"onPremisesSecurityIdentifier": null,
"otherMails": [
"[email protected]"
],
"passwordPolicies": null,
"passwordProfile": {
"password": null,
"forceChangePasswordNextLogin": true,
"enforceChangePasswordPolicy": false
},
"physicalDeliveryOfficeName": null,
"postalCode": null,
"preferredLanguage": null,
"provisionedPlans": [],
"provisioningErrors": [],
"proxyAddresses": [],
"refreshTokensValidFromDateTime": "2017-11-02T13:48:09Z",
"showInAddressList": null,
"signInNames": [],
"sipProxyAddress": null,
"state": null,
"streetAddress": null,
"surname": "Surname",
"telephoneNumber": null,
"usageLocation": null,
"userIdentities": [],
"userPrincipalName": "cpim_662effe2-cd73-4f4a-8b42-2af5f68b2db1@qa2clientb2ctheaccesshub.onmicrosoft.com",
"userType": "Member"
}
In summary we notice:
Standard User
Guest User
Local User
Social User
Although we can see some trends we'd rather not make guesses or bad assumptions. So we have the following questions:
1) What is the best way to know which of the four types of user we're looking at?
2) Is there a simpler way to get the login (ideally as one field, we're trying to build this into a simple mapping)? Other than :
If signInNames[0].value is not null use signInNames[0].value
Elseif otherMails[0] is not null use otherMails[0]
Else userPrincipalName
3) Do other social users behave differently? (We've only done Facebook so far.)
4) For social users what is the best way of knowing which identity provider is used?
UPDATE 11/8:
5) Why don't LocalAccount users get an otherMail value?
6) Why don't non-LocalAccount users get sign-in names? I guess that was added exclusively for B2C?
7) Also I guess end users don't really have the ability to associate social and local accounts today? (unless using an app which leverages graph).
UPDATE 11/8 #2:
8) Is it safe (for now) to assume that I can disambiguate a B2C signed-up individual who can only login with a social account using the following criteria:
Or would there be a better method?
1) These are not mutually exclusive properties. For example, a local account, guest or member user can also link their multiple social profiles to their account. The underlying data (available via Graph API) does not make any assumptions.
The most reliable way to know whether an account is a local user or not is to look at creationType property. You can also look at signInNames. Having said that, that would not imply that the user will not have their social profile linked in the future. It's just that this functionality is not exposed in B2C standard policies.
2) signInNames
is certainly used for signing in the user. However, a user can also have more than one signInName
as far as the directory is concerned. That is why signInNames
is a collection. So a utility company or bank may have an account id and email address both as signInNames
.
otherMails
is not a property used to sign in a user. So you may want to skip that. Think of otherMails
as an email address that is not used for any critical function by the directory (e.g. it's not used for sign in or for password resets etc.)
You would use userPrincipalName
for work accounts.
3) All social accounts are considered as external user identities, and are mapped in the same way. They are not yet available through Graph API, but when they are, they will appear as a collection too since a user may link to multiple social accounts.
4) Not possible today, but coming in the future.
UPDATE 11/8 to answer additional questions
5) Simply because there is no email address in B2C standard flow to add in otherMails
for local account users. The email address they used to sign up is already in signInNames
property. Through Graph API, you could add otherMails
, and when a token is issued, it will show up in the emails
claim even for local account users.
6) Non-local account users do not get a signInName
because they do not need to sign in using a signInNames
. Work accounts use the userPrincipalName
instead which can be tied to Office 365, Exchange, or on-premise AD. Social IDPs have external identities. So, there is no known scenario in which signInNames
needs to be used for non-local accounts at least today.
7) Yes, it is not possible today to link a social account with a local account through standard policies, but can be done through custom policies. It is also not yet possible through Graph API (because external identities are not yet exposed), but will be possible in the future when the API is exposed.
Update 11/24:
8) If you look at user principal name, you will notice it is random. cpim_ can be removed any time, it is not part of the contract. In fact, IMHO, it should be removed already so apps do not take a dependency on it.
To determine this correctly, we are expecting to announce a new "userIdentities" property on the user account, using which you can determine which social IDPs is the user account linked to. That would be the best way to do this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With