In my scenario there is a Azure Application Registration (client_app) with credentials. This application is used to request an oauth2 access token. A second Application Registration (main_app) is the scope, which is providing App Roles and more.
My goal is to include information from client_app in a jwt token claim, when requesting the token using the client credential flow on an azure /oauth2/v2.0/token
endpoint.
This is a token request:
POST /<tenant id>/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=<Application ID of client_app>
&client_secret=<secret of client_app>
&scope=<Application ID URI of main_app + "/.default">
&grant_type=client_credentials
The response of the token request is a valid token including roles
(granted App Roles from main_app to client_app), aud
, sub
and idtype: app
claims. But my optional claims are missing.
MS docs state, that it is possible to include optional claims using directory extensions
Schema and open extensions are not supported by optional claims, only the AAD-Graph style directory extensions. This feature is useful for attaching additional user information that your app can use [...]
So I tried adding a directory extension to my main_app (extension_<main_app ID>_someAttrName
), added an optional claim to main_app and stored a value to this directory extension in client_app. Unfortunately the extension value is not reflected in the token.
I tried adding the directory extension on the client_app itself (extension_<client_app ID>_someAttrName
) and map the directory extension to main_app by using ClaimsMappingPolicy. Unfortunately the extension value is not reflected in the token.
It seems to me, that the claims are missing, since they do not originate from a user object. MS docs provide a lot of information to add optional claims, but most scenarios involve a user. E.g. the optional claims source
property of a manifest:
The source (directory object) of the claim. There are predefined claims and user-defined claims from extension properties. If the source value is null, the claim is a predefined optional claim. If the source value is user, the value in the name property is the extension property from the user object.
As I understand it, only null
and "user"
is supported. I want to include a directory extension originating from an Application Registration (client_app). And since I have to use the client credential flow (server-to-server) there is no user involvement.
So how do I add custom optional claims to a token when using client credential flow, when no user object is involved? There is an application object involved. So how do I need to configure claims to reflect custom application data (e.g. directory extensions)?
Manifest of main_app:
{
"id": "<main_app ID>",
"acceptMappedClaims": true,
"accessTokenAcceptedVersion": 2,
"appId": "<main_app APP ID>",
"appRoles": [{
"allowedMemberTypes": ["Application"],
"isEnabled": true,
"origin": "Application",
"value": "readAll"
},{
"allowedMemberTypes": ["Application"],
"isEnabled": true,
"origin": "Application",
"value": "writeAll"
}],
"identifierUris": ["api://<main_app ID>"],
"optionalClaims": {
"idToken": [],
"accessToken": [ {
"name": "extension_<main_app ID>_someAttrName",
"source": "application", <-- propably invalid, "user" or null is supported
"essential": false,
"additionalProperties": []
}, { ... }
],
},
} /# trunced for readability
Manifest of client_app:
{
"id": "<client_app ID>",
"accessTokenAcceptedVersion": null,
"appId": "<client_app APP ID>",
"passwordCredentials": [{...}],
"extension_<main_app ID>_someAttrName": "some-string-value"
} /# trunced for readability
My ClaimsMappingPolicy definition
looks like this:
"ClaimsMappingPolicy": { "Version": 1, "IncludeBasicClaimSet": "true",
"ClaimsSchema": [ {
"Source": "application",
"ExtensionID": "extension_<client_app ID>_someAttrName",
"JwtClaimType": "someAttrName"
} ]
}