Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to identify different kinds of users in azure b2c

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:

  • Standard User
  • Guest User (B2B)
  • Local User (B2C user who signed up with email)
  • Social User (B2C user who signed up with a social account)

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

  • the userPrincipalName looks normal
  • the login email can be found at: userPrincipalName
  • creation type is: null

Guest User

  • the userPrincipalName looks to be the external directories upn followed by '#EXT#' followed by this primary domain
  • the login email can be found at: otherMails[0]
  • creation type is: null

Local User

  • the userPrincipalName looks to be some object id followed by this primary domain
  • the login email can be found at: signInNames[0].value
  • creation type is: LocalAccount

Social User

  • the userPrincipalName looks to be 'cpim_' followed by some object id followed by this primary domain
  • the login email can be found at: otherMails[0]
  • creation type is: null

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:

  • creation type is: null - meaning they're not a Local User
  • userPrincipalName is prefixed with 'cpim_' followed by a UUID - meaning they are PROBABLY not a Standard or Guest user

Or would there be a better method?

like image 846
fei0x Avatar asked Jan 04 '23 06:01

fei0x


1 Answers

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.

like image 165
Omer Iqbal Avatar answered Jan 05 '23 19:01

Omer Iqbal