Working with custom/extension attributes in Entra External ID (CIAM)

Rory Braybrook
The new control plane
5 min read3 days ago
Image of an extension cord
”Europlug extension cord” — Peter Lebbing — Wikimedia

This is a utility post, as I will post more about extension attributes, but it can live on its own.

The scenario is that I want to have a web. app calling an API and adding the extension attribute to the tokens.

The sample is here.

Extension attributes are where you want to add a new attribute to the schema.

A common one is "EmployeeID"

Here is the PowerShell 7 script to create an extension attribute.

Note that we are linking the extension attribute to the API application using the application's objectID.

# Define your Azure AD tenant ID, client ID, and client secret
$tenantId = "tenant.onmicrosoft.com"
$clientId = "cf9...bfc"
$clientSecret = "6~_...ae."

# Define the resource and scope
$scope = "https://graph.microsoft.com/.default"

# Define the URL for obtaining the access token
$url = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"

# Define the body for the API request
$body = @{
"client_id" = $clientId
"scope" = $scope
"client_secret" = $clientSecret
"grant_type" = "client_credentials"
}

# Send the API request and store the response
$response = Invoke-RestMethod -Uri $url -Method Post -Body $body

# Extract the access token from the response
$accessToken = $response.access_token

# Display the access token
# Write-Output "Access Token: $accessToken"

# Define the application object ID
$appId = "850...84d"

# Define the Graph API URL for extensionProperties
$url = "https://graph.microsoft.com/v1.0/applications/$appId/extensionProperties"

# Define the headers for the API request
$headers = @{
"Authorization" = "Bearer $accessToken"
"Content-Type" = "application/json"
}

# Define the body for the API request
$body = @{
"name" = "EmployeeID"
"dataType" = "String"
"targetObjects" = @("User")
} | ConvertTo-Json

# Send the API request and store the response
$response = Invoke-RestMethod -Uri $url -Headers $headers -Method Post -Body $body

# Display the response
$response

This uses client credentials, so you need to create a separate app. registration with a secret to support the call.

Now, let's display the extension attributes attached to the application.

Here is the PowerShell 7 script.

# Define your Entra External ID tenant ID, client ID, and client secret
$tenantId = "tenant.onmicrosoft.com"
$clientId = "cf9...bfc"
$clientSecret = "6~_...ae."

# Define the resource and scope
$scope = "https://graph.microsoft.com/.default"

# Define the URL for obtaining the access token
$url = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"

# Define the body for the API request
$body = @{
"client_id" = $clientId
"scope" = $scope
"client_secret" = $clientSecret
"grant_type" = "client_credentials"
}

# Send the API request and store the response
$response = Invoke-RestMethod -Uri $url -Method Post -Body $body

# Extract the access token from the response
$accessToken = $response.access_token

# Display the access token
# Write-Output "Access Token: $accessToken"

# Define the application objectID
$appId = "850...84d"

# Define the Graph API URL for extensionProperties
$url = "https://graph.microsoft.com/v1.0/applications/$appId/extensionProperties"

# Define the headers for the API request
$headers = @{
"Authorization" = "Bearer $accessToken"
"Content-Type" = "application/json"
}

# Send the API request and store the response
$response = Invoke-RestMethod -Uri $url -Headers $headers -Method Get

# Display the extensionProperties
$response.value | Format-Table -Property Name, DataType, TargetObjects

This results in:

name                                                  dataType targetObjects
---- -------- -------------
extension_276...f43_EmployeeID String {User}

Now, let's link this attribute to a user.

Here is the PowerShell 7 script.

# Define your Entra External ID tenant ID, client ID, and client secret
$tenantId = "tenant.onmicrosoft.com"
$clientId = "cf9...bfc"
$clientSecret = "6~_...ae."

# Define the resource and scope
$scope = "https://graph.microsoft.com/.default"

# Define the URL for obtaining the access token
$url = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"

# Define the body for the API request
$body = @{
"client_id" = $clientId
"scope" = $scope
"client_secret" = $clientSecret
"grant_type" = "client_credentials"
}

# Send the API request and store the response
$response = Invoke-RestMethod -Uri $url -Method Post -Body $body

# Extract the access token from the response
$accessToken = $response.access_token

# Display the access token
# Write-Output "Access Token: $accessToken"

# Define the user's UPN
$upn = "b1c...7e3@tenant.onmicrosoft.com"

# Define the Graph API URL for the user
$url = "https://graph.microsoft.com/v1.0/users/$upn"

# Define the headers for the API request
$headers = @{
"Authorization" = "Bearer $accessToken"
"Content-Type" = "application/json"
}

# Define the body for the API request
$body = @{
"extension_276...f43_EmployeeID" = "098765"
} | ConvertTo-Json

# Send the API request and store the response
$response = Invoke-RestMethod -Uri $url -Headers $headers -Method Patch -Body $body

# Display the response
$response

Unfortunately, I cannot find a way to display the user attributes that include the extension attribute.

Here is the PowerShell 7 script to display some of the user attributes.

# Define your Entra External ID tenant ID, client ID, and client secret
$tenantId = "tenant.onmicrosoft.com"
$clientId = "cf9...bfc"
$clientSecret = "6~_...ae."

# Define the resource and scope
$scope = "https://graph.microsoft.com/.default"

# Define the URL for obtaining the access token
$url = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"

# Define the body for the API request
$body = @{
"client_id" = $clientId
"scope" = $scope
"client_secret" = $clientSecret
"grant_type" = "client_credentials"
}

# Send the API request and store the response
$response = Invoke-RestMethod -Uri $url -Method Post -Body $body

# Extract the access token from the response
$accessToken = $response.access_token

# Display the access token
# Write-Output "Access Token: $accessToken"

# Define the user's UPN
$upn = "b1c...7e3@tenant.onmicrosoft.com"

# Define the Graph API URL for the user
$url = "https://graph.microsoft.com/v1.0/users/$upn"

# Define the headers for the API request
$headers = @{
"Authorization" = "Bearer $accessToken"
"Content-Type" = "application/json"
}

# Send the API request and store the response
$response = Invoke-RestMethod -Uri $url -Headers $headers -Method Get

# Display the user's attributes
$response | ConvertTo-Json -Depth 4

This results in:

{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
"businessPhones": [],
"displayName": "Easy Auth",
"givenName": "Easy",
"jobTitle": null,
"mail": null,
"mobilePhone": null,
"officeLocation": null,
"preferredLanguage": null,
"surname": "Auth",
"userPrincipalName": "b1c...7e3@tenant.onmicrosoft.com",
"id": "b1c...7e3"
}

I also have a utility that does this via the old AzureAD API commands.

Using that utility, I can display the extension attributes for the user, e.g.:

"externalUserConvertedOn": null,
"externalUserState": null,
"externalUserStateChangeDateTime": null,
"userType": "Member",

"extension_276...f43_EmployeeID": "098765",
"extension_a3d...8c4_Custom": "123456",

"employeeOrgData": null,
"passwordProfile": null,
"assignedLicenses": [],

This is where it gets confusing 😢

The “Custom” extension attribute was created using the old B2C method and the utility above, which involved using the clientID of the B2C extension application.

Display name b2c-extensions-app. Do not modify. Used by AADB2C for storing 
user data.

Application (client) ID
a3d...8c4

However, the “EmployeeID” extension attribute was created using the PowerShell above, which involved using the API application's clientID.

ToDo API 276...f43

All good!

--

--

Rory Braybrook
The new control plane

NZ Microsoft Identity dude and MVP. Azure AD/B2C/ADFS/Auth0/identityserver. StackOverflow: https://bit.ly/2XU4yvJ Presentations: http://bit.ly/334ZPt5