blog.atwork.at

news and know-how about microsoft, technology, cloud and more.

Working with Azure AD schema extensions and Microsoft Graph

Azure Active Directory is Microsoft´s Cloud Identity system that stores user, license, group, apps, device data and more data in a secure way. As developers, we can extend many of these resources with custom extension. This can be useful to store additional metadata, such as a cost center or personal data for a user, for a group or other resource types. Unfortunately, there isn't much documentation on that. Here's how to do this step-by-step for the user resource with Azure AD schema extensions in real life.

Predefined (user) properties in Azure AD

The Azure AD stores the identity and management data of a Microsoft 365 tenant. Every resource has a predefined schema that describes the object.

For a user object, we can use the default (predefined) user properties, such as the User Principal Name (UPN), the name, the job title, etc. The following screenshot in the Azure portal shows a user in a demo tenant with it´s identity data and some properties with the job info. We see that Miriam is a Director and works in the Sales and Marketing department.

image

Tip: To get your own M365 tenant and a playground, join the Microsoft 365 Developer Program.

Why extend the Azure AD

In many organizations, the existing user properties do not offer enough space to save all relevant personnel information. So, we want to add additional information in the central Azure AD. For example, we want to add a person´s cost center, and a Personal Identification Number (or similar metadata). For such small and simple metadata, there´s no need to use an extra database or an external data store when we can use the Azure AD for little text data.

Here, we add properties for "costcenter" and "pin" (Personal Identification Number) to all users. After the extension has been added, we want to fill that data and use that data in our own apps. Microsoft often names such apps "line of business" (LOB) applications. We can access this data from any app using Microsoft Graph.

Extensions types for Azure AD

Let´s have a look what extensions are available in Azure AD. See Extension attributes for Azure Active Directory.

  1. [AzureAD Graph extension attributes: These allow to store attribute values for users, tenant details, devices, applications, and service principals, but are deprecated. See extension-attributes]
  2. Azure AD Open extensions: These are open types that offer a flexible way to add untyped app data directly to a resource instance, see open-extensions
  3. Azure AD Schema extensions: These define a schema that can be used to extend a resource type, see schema-extensions

Since 1. Azure AD Graph API is deprecated, and the support ends by June 2022, we should choose between 2. and 3, depending on the desired usage. In our sample, we go for schema extensions. We want that every user in the tenant has our custom properties available.

Access the Microsoft.Graph

To start working with Azure AD schema extensions, we need a tool to access the Azure AD. We can do this with Microsoft Graph REST API calls, with an app using a Microsoft Graph SDK, or with the Microsoft.Graph PowerShell module from the PowerShell gallery. For all methods, we need an Azure AD application, see below.

I would like to take this opportunity to thank my colleague and PowerShell guru Christoph Wilfing and Martina Grom here for playing around with me and finding the solutions. It is fun to discover and implement solutions together.

Use the schema extension methods

First, we need to create a new schema extension for our desired user properties. When checking out the Graph documentation, we see that the methods to manage schema extensions unfortunately are not supported with  Application permissions...

Update: The documentation Create schemaExtension says, this method can currently be used only with Delegated permissions - but it´s not correct. As of today, it works with app permissions as well!

image

So, the docs should be updated...

Install the Microsoft.Graph PowerShell module

PowerShell is a good method to test that. So, we install the Microsoft.Graph PowerShell module from the PowerShell Gallery first:

Install-Module Microsoft.Graph -Scope CurrentUser

To upgrade an existing older version, use Update-Module Microsoft.Graph -Force. You can check the version with Get-InstalledModule Microsoft.Graph.

Currently (as of February 2021), version 1.3.1 is the latest version. Run pwsh.exe to install the Microsoft.Graph module for PowerShell Core (To use PS Core, you can download and install the package from https://github.com/PowerShell/PowerShell).

Sign-in to Graph

The default login flow is using device code followed by the user login. To get all permissions we need, we use the permissions from the documentation as scope (and the TenantId to ensure we login to the desired tenant).

Connect-MgGraph -TenantId "<company>.onmicrosoft.com" -Scopes "User.ReadWrite.All", "Group.ReadWrite.All", "Application.ReadWrite.All", "Directory.AccessAsUser.All", "Directory.ReadWrite.All"

We login as a Global Admin of the tenant. After the successful login, we can use Get-MgContext to see the authentication details.

ClientId              : 14d82eec...
TenantId              : f89d8982...
CertificateThumbprint :
Scopes                : {Application.ReadWrite.All, Directory.AccessAsUser.All, Directory.ReadWrite.All, Group.ReadWrite.All...}
AuthType              : Delegated
CertificateName       :
Account               : admin@sometenant.onmicrosoft.com
AppName               : Microsoft Graph PowerShell
ContextScope          : CurrentUser
Certificate           :

Now we can start to use the Graph commands against the tenant´s Azure AD.

Create an app in Azure AD

Before we can create a new schema extension, we also need an Azure AD application. The note at Create schemaExtension is important: "Additionally for the delegated flow, the signed-in user must be the owner of the calling application OR the owner of the (application with the) appId used to set the owner property.". We will come back to this below.

So, we need to create a new application in Azure portal. The Tutorial: Register an app with Azure Active Directory (Microsoft Dataverse) - Power Apps | Microsoft Docs shows the required steps. The app name here is tpe5schemaextenison.

image

When registered, we add the app permissions that we need for our tasks: We add Microsoft Graph and the delegated permissions for: User.ReadWrite.All, Application.ReadWrite.All, Directory.AccessAsUser.All, Directory.ReadWrite.All as here.

image

Then, we need the appId. In our sample it´s 992cc0fe...

image

We´re done here then.

Create user schema extensions

Now we can use the Microsoft.Graph PowerShell commands to create the desired user schema extension. Before, we can list all existing schema extensions with Get-MgSchemaExtension.

The Graph documentation informs how to create a new extension with REST calls. Here´s the Microsoft.Graph PowerShell equivalent (remember, we currently need to do this as a user. Find the full script here):

# After authenticating, we create a new, empty ArrayList
$SchemaProperties = New-Object -TypeName System.Collections.ArrayList

# define our keys and the types

$prop1 = @{
'name' = 'costcenter';
'type' = 'String';
}
$prop2 = @{
'name' = 'pin';
'type' = 'Integer';
}

# and add them to the SchemaProperties
[void]$SchemaProperties.Add($prop1)
[void]$SchemaProperties.Add($prop2)

# Now we can create the new schema extension for the resource User. Our Azure AD app is the owner.
$SchemaExtension = New-MgSchemaExtension -TargetTypes @('User') `
    -Properties $SchemaProperties `
    -Id 'myapp1' `
    -Description 'my organization additional user properties' `
    -Owner "992cc0fe..."

BTW, the property type definitions are case sensitive! Available types are:
Binary, Boolean, DateTime, Integer, and String. Binary can contain 256 bytes maximum, String can contain 256 characters maximum. So, such  properties are only for small data. For large data, we recommend to use the properties with an ID or a link value and use an external data storage.

When the command above was successful, the schema extension myapp1 was created. We can now ask for our custom extension with the generated id:

# Check the new schema extension:

Get-MgSchemaExtension -SchemaExtensionId $SchemaExtension.Id | fl

The result looks as here. The schema extension gets an Id with the pattern "ext<random 8 characters>_<appname>". Here it´s ext2irw6qzw_myapp1.

image

The extension includes our two properties costcenter and pin.

Great. Now, every user object has these properties available. To be complete, let´s see how we can manage the existing extension.

Important update schema extensions learnings

Let´s say, we want to add another property to the schema extension. Well, this is done with the Update-MgSchemaExtension command. See the documentation at  Update schemaExtension.

This was tricky and it took us some time to figure it out: The sample code and documentation does not state that the owner of the extensions is required in the request to be successful. Without the owner a write-error is thrown... Here´s a sample request trying to change the status of the schema extension (see below):

Wrong request: (using Microsoft.Graph Powershell)
Update-MgSchemaExtension -SchemaExtensionId $SchemaExtension.Id -Status 'Available'

ERROR:
Update-MgSchemaExtension : Cannot perform this extension schema write operation.

Correct request:
Update-MgSchemaExtension -SchemaExtensionId $SchemaExtension.Id -Status 'Available' -Owner $OwnerAppID

That's why we submitted a problem in https://github.com/microsoftgraph/microsoft-graph-docs at The owner of the extensions needs to be added #11131.

Update: As we learned, one application can only create up to 5 schema extensions. Keep that in mind!

Update schema extensions (when "InDevelopment")

With this knowledge, to add the property "isdirector" as a Boolean field, we need to take care of two things: We have to send the full schema and it is required to add the owner appid again, as here. We assume, we still have the $SchemaProperties and the $SchemaExtension.Id filled. Otherwise we have to fill the data again.

# Create a new (3rd) schema property
$prop3 = @{
'name' = 'isdirector';
'type' = 'Boolean';
}

# add our new property to our existing ArrayList (that already includes 2 other properties)
$SchemaProperties.Add($prop3)

# Update the schema extension with a) the full schema properties list and b) the owner appid!
Update-MgSchemaExtension -SchemaExtensionId $SchemaExtension.Id `
    -Properties $SchemaProperties `
    -Owner "992cc0fe..."

Again, let´s check the schema extension:

# Check the new schema extension:

Get-MgSchemaExtension -SchemaExtensionId $SchemaExtension.Id | fl

# If we do not have the Id, let´s search for the app:
Get-MgSchemaExtension -All | ? id -like '*myapp1' | fl

We see the third property isdirector added.

image

Change the schema extension status to "Available"

In the *-MgSchemaExtension ouput above, we see that the schema extension is in status "InDevelopment". When we´re done and sure that this is final, we should change that to the operational status "Available". Then, we can no longer delete properties in the schema or the schema itself. We can only deprecate it.

We can update the status as here: Note, that the status is also case-sensitive: "Available"

# Update the schema extension to be final
Update-MgSchemaExtension -SchemaExtensionId $SchemaExtension.Id `
    -Status 'Available' `
    -Owner "992cc0fe..."

image

Note that such schema extensions in Status "Available" cannot be deleted!

Delete a schema extension

If a schema is no longer required and the status is not set to"Available", we can use the Remove-command as described at Delete schemaExtension and shown as here. Note: "The signed-in user can only delete schemaExtensions they own (where the owner property of the schemaExtension is the appId of an application the signed-in user owns)."

# Remove a schema extension
Remove-MgSchemaExtension -SchemaExtensionId $SchemaExtension.Id

If the operation was successful, no response is sent. Azure AD prohibits the removal if the schema extension is final, as here:

image

We´re done with the creation and management now. Let´s see how to use the new schema extension.

Update: We ran into some issues when deleting schema extensions. Sometimes, the schema extension is removed, and sometimes not. We got different results in in the REST API and in PowerShell. Also, the PowerShell Get-MgSchemaExtension -Filter does not work properly, e.g. when running Get-MgSchemaExtension -Filter "description eq 'some-description'". We reproduced and documented that issue and opened a case for that... You can find some more issues at Get-MgUser not returning extension attributes #236. It seems, there´s still some work to do in the PowerShell module and in the API.

Simulate a LOB app

Let´s assume, we have a custom LOB app that is using the custom user schema extension costcenter, pin and isdirector. We simulate the work of the app with Graph Explorer here. We sign-in, set the permissions (as above) and try out the REST requests.

We search for Megan to get her User ID: We run a HTTP GET operation:
https://graph.microsoft.com/v1.0/users?$filter=startswith(displayName,'Megan')

image

We find the User ID. In our sample, we can use the found ID to get her user object:
https://graph.microsoft.com/v1.0/users/50e6cdd9...

We see the standard user properties (and can select more with the $select OData query parameters). See more about the Graph OData parameters at OData system query options. So far so good.

Set the schema extension properties with Microsoft Graph

In contrast to the definition of the schema extensions, accessing the schema extensions as application does work. To make this clear: once existing, apps can use the schema definitions with Microsoft Graph.

To fill the custom schema extension properties, we use a HTTP PATCH operation as here: We need the schema extension Id ext2irw6qzw_myapp1 and the properties. Not all properties must be provided, we could set one single property as well. So, the request contains a body with the data in JSON format.
PATCH https://graph.microsoft.com/v1.0/users/50e6cdd9...

{
"ext2irw6qzw_myapp1": {
  "costcenter": "K100",
  "pin": 1220,
  "isdirector": true }
}

When successful, we get back HTTP 204 as here.

image

If we send false data (that does not correspond to the property type definitions), we get a HTTP 400 Bad request result.

Get the schema extension data

To get the custom data, we run a HTTP GET operation and select the schema extension id ext2irw6qzw_myapp1:

https://graph.microsoft.com/v1.0/users/50e6cdd9...?$select=ext2irw6qzw_myapp1

As a result, we get the custom data, as we see here.

image

If the data is not filled for a user, the request does not deliver any schema extension information. To get all users with the schema extension data, we can run a GET operation with the select parameter as here:

https://graph.microsoft.com/v1.0/users/?$select=ext2irw6qzw_myapp1,userprincipalname

image

So, this is how we can work with the custom data of myapp1 in a LOB app.

Update: Find all users with specific custom property data

We played around for getting the correct syntax for querying for custom property data. This Graph query returns all users with a specific value in one of the properties and outputs only the required data, like the user and the custom data.

We want to get all users where the schema extension cost center is starting with "K100". Here´s the appropriate OData query against the Graph REST interface.

https://graph.microsoft.com/v1.0/users?$filter=startswith(ext2irw6qzw_myapp1/costcenter, 'K100')&$select=displayName,id,description,ext2irw6qzw_myapp1

So, we can filter our own data with the Graph easily.

Can we use a Dynamic Group with rules with schema extensions

Of course, the question has arisen, if we can use the user schema extensions in Dynamic Groups aka in a Security Group with dynamic membership. So, we tried this, and created a new Security Group with Dynamic User membership type.

image

Here, we added the appId. So far, this sounds logically. The appId was accepted.

image

We wanted to include all users that have a specific costcenter set. The syntax for our query would look as here (at least, we expected that it works in the same way as other rules):

(user.ext2irw6qzw_myapp1_costcenter -eq "K100")

image

Unfortunately, this does not work. The Azure portal does not accept the query. The documentation informs about the supported extensions at Dynamic membership rules for groups in Azure Active Directory: Extension properties and custom extension properties.

image

To make it short: Obviously, only custom extension properties are supported, that are synced from on-premises Windows Server AD or from a connected SaaS application and are of the format of user.extension_[GUID]_[Attribute]. We did our research and tried various queries, but all of them failed. It appears that dynamic groups do currently not support schema extensions. We hope, this "feature" of Dynamic Groups will be possible in future.

Summary

Azure AD schema extensions allow to extend the Azure AD for various resource types, such as users, groups, messages, devices and more. Organizations can define their custom schema extensions and properties and use them in the same way as built-in properties with Microsoft Graph.

image

We did not find full documentation or samples how to use them properly. So we researched for a day and put together all the currently relevant information here. We hope this article clarifies when it makes sense to use schema extensions and understand how to use them for your real-world scenarios. You can find the mentioned PowerShell script in my Github repository office365scripts. We ourselves will also integrate schema extensions in our own solutions where it makes sense.

Happy testing and coding!

Comments (2) -

  • Carsten

    1/21/2021 2:50:28 PM |

    Thank you for the very helpful instructions.
    I have tried to follow the steps but when I call New-MgSchemaExtension I get the following message:
    New-MgSchemaExtension : Method not allowed.

    I am the owner of the application and also an administrator in the entire Test Azure portal.Are there any other restrictions that prevent me from extending the schema?


    Thanks in advance

  • Jose

    3/25/2021 6:49:58 PM |

    Can you use the PowerShell Graph SDK to query users and the extension schema property values they have? I can query users and I can find the schema extension properties using get-mguser and get mg-schemaextension but i cannot find a way to search for a user and return back their extended schema values.

Pingbacks and trackbacks (1)+

Loading