Schema Extensions and Sync Options Entra-DS and Entra-ID

Schema Extensions and Sync Options Entra-DS and Entra-ID

April 10, 2025

Background

Extending the schema in Directory Services is crucial for organizations seeking to enhance their identity and access management capabilities. The default schema may not always accommodate unique business requirements or emerging technologies. By extending the schema, organizations can introduce custom attributes and classes, enabling more granular control over user identities, groups, and resources. This flexibility allows for better alignment with specific operational needs, improved data management, and enhanced security protocols.

While we at WedaCon have decades of experience extending the schema for Directory Services in general, such as Novell/NetIQ/Micro Focus/OpenText/(choose your name) eDirectory, openDS/ openDJ or Active Directory, we recently got a request from a customer to investigate the schema extension capabilities and synchronization options for Microsoft EntraID and EntraDS (lets say: the ‘LDAP’ Interface Option available). Lets concentrate on EntraID first, or more specifically on how to access and modify them in the backend, which is Microsoft Graph.

Schema Extension Options

There are many resources and articles available explaining which options are available in the Entra space, the best we have found (so far) on this topic is in developer documentation for MS Graph, the section dealing with ‘Extensions

(WARNING: You should not use the variants below to store sensitive informations. There are other options for those specific use cases, see Custom Security Attributes or Custom authentication extensions for more information).

Lets start with a rough, high level overview

TypeExplainerScopeManaged via‘Linked’ to an owner application
Extension Attributes15 pre-defined extensionAttributes, ready to be used out of the boxUser ObjectDevice ObjectGraphAPIExchange Admin Center(+ EntraIDSync)’no’ (at least this is what Microsoft pretends…)
Directory Extensionsspecific extension based on an applicationUser, group, administrativeUnit, application, device, organizationGraphAPIyes
Schema Extensionsspecific extension based on an applicationUser, group, administrativeUnit, contact, device, event, message, organization,postGraphAPIyes
Open Extensionsspecific extension based on an applicationUser, group, contact, device, event, message, organization,post, todoTask, todoTaskListGraphAPIyes

(for a full overview, check here)

As a start, we will concentrate and investigate on the following use cases:

  1. Check availability and usage of ‘build-in’ extension Attributes
  2. Check functionality of ‘Directory Extensions’
  3. Synchronization and usage options of extensions in Entra DS

Extension Attributes

Most likely, you are familiar with those. For decades, the extensionAttributes1 - extensionAttributes15 were available in Active Directory, and if you synchronize them to Azure / Entra using AADSync, AADConnect, Entra Connect or whatever name we currently have for this, you will find them as onPremisesExtensionAttributes.

But those attributes are available even though you might not have a synchronization to your onPremise Domain set up. Lets check via Microsoft Graph (we do use the Graph Explorer here against one of our test tenants)

(NOTE: We assume that you are familiar with the Graph Explorer)

So lets first check the current status of a test user:

https://graph.microsoft.com/v1.0/users/[userid]

image.png

The reply contains standard attributes only, so lets check for specific attributes

https://graph.microsoft.com/v1.0/users/[userid]?$select=id,displayName,onPremisesExtensionAttributes

image.png

As you see, the attributes are all available, even though we do not have any onPremise setups, as you can see when checking for the respective attribute onPremiseSyncEnabled

GET https://graph.microsoft.com/v1.0/users/[userid]?$select=id,displayName,onPremisesExtensionAttributes,onPremisesSyncEnabled

image.png

So the well known extensionAttributes seem to be linked to onPremisesExtensionAttributes, even though there is no on-Premise setup. And as long as you do not enable this (so everything is ‘cloud-only’), you can use them as you like.

As soon as you enable on-prem functionality (so configure, enable and sync with your on-prem domain), the extensionAttributes are ‘handed over’ to the onPremisesSync setups: User and Device Objects synchronized will get the flag onPremiseSyncEnabled set to true which means you cant manage them via GraphAPI any more. But as long as that does not happen, we can use them:

Lets patch our test identity and set some values:

PATCH https://graph.microsoft.com/v1.0/users/[userid]
Body
{
	"onPremisesExtensionAttributes": {
		"extensionAttribute1": "something",
		"extensionAttribute2": "whatever"
	}
}

image.png

And (after some time), we can also check and view these attribute via the portal

image.png

Challenges with extensionAttributes

So all fine? Not really. As can be seen in the schema overview table, Microsoft states that the extensionAttributes are NOT linked to a specific applications, contrary to all the other extension options. The important part of that statement is the word ‘specific’.

The structure implies that the extensionAttributes are part of a group called onPremisesExtensionAttributes. And this is effectively the application which controls the extensions. As long as ’this application’ is not enabled (so DirSync is not enabled) you can use them in read/write style. If onPrem-Sync is added to the mix, they become ‘read-only’ for any other action.

Directory Extensions

A more complex, but also more flexible way to deal with extension are Directory Extensions. In general, the extension mechanism is the same as with extensionAttributes, but here we define and create a dedicated application in Entra, which really owns the attributes.

Directory extensions are strongly typed, discoverable and filterable. The first step to create such an extension is to create an application which ‘owns’ it

Lets create a simple application with default values

POST https://graph.microsoft.com/v1.0/applications
Content-type: application/json
{
  "displayName": "DirectoryExtensionApp"
}

The request will respond with the default values and the id

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#applications/$entity",
    "id": "7f8f1663-facf-4902-a9c3-4537f9169883",
    "deletedDateTime": null,
    "appId": "f78bae68-9bdb-4d02-8ed6-01d233bece6e",
    "applicationTemplateId": null,
    "disabledByMicrosoftStatus": null,
    "createdDateTime": "2025-04-11T11:42:25.8798885Z",
    "displayName": "DirectoryExtensionApp",
    "description": null,
    "groupMembershipClaims": null,
    "identifierUris": [],
    "isDeviceOnlyAuthSupported": null,
    "isFallbackPublicClient": null,
    "nativeAuthenticationApisEnabled": null,
    "notes": null,
    "publisherDomain": "2wd.eu",
    "serviceManagementReference": null,
    "signInAudience": "AzureADMyOrg",
    "tags": [],
    "tokenEncryptionKeyId": null,
    "uniqueName": null,
    "samlMetadataUrl": null,
    "defaultRedirectUri": null,
    "certification": null,
    "optionalClaims": null,
    "servicePrincipalLockConfiguration": null,
    "requestSignatureVerification": null,
    "addIns": [],
    "api": {
        "acceptMappedClaims": null,
        "knownClientApplications": [],
        "requestedAccessTokenVersion": null,
        "oauth2PermissionScopes": [],
        "preAuthorizedApplications": []
    },
    "appRoles": [],
    "info": {
        "logoUrl": null,
        "marketingUrl": null,
        "privacyStatementUrl": null,
        "supportUrl": null,
        "termsOfServiceUrl": null
    },
    "keyCredentials": [],
    "parentalControlSettings": {
        "countriesBlockedForMinors": [],
        "legalAgeGroupRule": "Allow"
    },
    "passwordCredentials": [],
    "publicClient": {
        "redirectUris": []
    },
    "requiredResourceAccess": [],
    "verifiedPublisher": {
        "displayName": null,
        "verifiedPublisherId": null,
        "addedDateTime": null
    },
    "web": {
        "homePageUrl": null,
        "logoutUrl": null,
        "redirectUris": [],
        "implicitGrantSettings": {
            "enableAccessTokenIssuance": false,
            "enableIdTokenIssuance": false
        },
        "redirectUriSettings": []
    },
    "spa": {
        "redirectUris": []
    }
}

Great, so we do have an application. Using that application, we can now define properties for it

POST https://graph.microsoft.com/v1.0/applications/7f8f1663-facf-4902-a9c3-4537f9169883/extensionProperties
    {
    "name": "myWDCStringExtension",
    "dataType": "String",
    "targetObjects": [
        "User"
    ]
}

This responds with

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#applications('7f8f1663-facf-4902-a9c3-4537f9169883')/extensionProperties/$entity",
    "id": "af003389-19eb-46ea-833d-a1d0d3404054",
    "deletedDateTime": null,
    "appDisplayName": "DirectoryExtensionApp",
    "dataType": "String",
    "isMultiValued": false,
    "isSyncedFromOnPremises": false,
    "name": "extension_f78bae689bdb4d028ed601d233bece6e_myWDCStringExtension",
    "targetObjects": [
        "User"
    ]
}

Quite simple, I have added another property (the correct term here) as type Integer. Lets check both properties

GET https://graph.microsoft.com/v1.0/applications/7f8f1663-facf-4902-a9c3-4537f9169883/extensionProperties

RESPONSE
{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#applications('7f8f1663-facf-4902-a9c3-4537f9169883')/extensionProperties",
    "@microsoft.graph.tips": "Use $select to choose only the properties your app needs, as this can lead to performance improvements. For example: GET applications('<guid>')/extensionProperties?$select=appDisplayName,dataType",
    "value": [
        {
            "id": "048db5c3-5714-46c7-864b-65c99b924273",
            "deletedDateTime": null,
            "appDisplayName": "",
            "dataType": "Integer",
            "isMultiValued": false,
            "isSyncedFromOnPremises": false,
            "name": "extension_f78bae689bdb4d028ed601d233bece6e_myWDCIntegerExtension",
            "targetObjects": [
                "User"
            ]
        },
        {
            "id": "af003389-19eb-46ea-833d-a1d0d3404054",
            "deletedDateTime": null,
            "appDisplayName": "",
            "dataType": "String",
            "isMultiValued": false,
            "isSyncedFromOnPremises": false,
            "name": "extension_f78bae689bdb4d028ed601d233bece6e_myWDCStringExtension",
            "targetObjects": [
                "User"
            ]
        }
    ]
}

Great, so lets try to add those new attributes to a user (The example below has another extension app and extension, to show a specific error you might run into)

image.png

image.png

So for some reason the new attribute is not reachable. Took me a while to figure out, but the reason for that is a missing API Permission we have to assign, as Microsoft Graph is not allowed to ‘see’ this new attribute. Lets add this right

image.png

If we now try again, we will get back success and can check the account:

image.png

EntraDS Synchronization

To make a long story short (I havent really fully configured this), but to add the attributes to the sync seems to be straight forward…

image.png

Conclusion

From an Identity Management and Synchronization perspective, using the OOTB extensionAttributes is a valid approach and for sure the ‘quickest’ way to achieve the goal to add custom data. However, due to the somewhat ‘moving ownership’ when it comes to dirsync capabilities, it might not be the best option. I would call that the ‘quick and dirty’ solution.

Having a dedicated application in Entra to hold directory Extensions is a much clearer approach and has the following benefits

  • typed attributes (binary, boolean, dateTime, integer, largeInteger, string)
  • MultiValue option
  • Different (defineable) target objects (user, group, administrativeUnit, application, device, organization)
  • Can be used in dirSync operations and claims
  • Can be removed
  • Application allows control (read/write operations) and ownership
  • up to 100 attributes (properties) can be set per application

If you have a requirement to use ‘complex attributes’ (eg an address structure with street, city, postalcode and other), directoryExtensions are limited. For that uses cases, you should check out schema Extensions, which works in a similar way.

Last updated on