6

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"
        } ]
}
Dan
  • 787
  • 1
  • 7
  • 20
  • Did you get any solution for this requirement, I am having the same requirement as like yours. – Saravana Kumar Aug 08 '21 at 17:16
  • @SaravanaKumar I just answered my own question – Dan Aug 09 '21 at 16:41
  • Hi, I have the same issue, I would like to return at least client application name in claims list, but found no way to do it. I find this behavior rather anoying... Thank you for investigation – kavanka Jan 06 '22 at 16:26

1 Answers1

2

I reached out to Azure support engineers, and they told me, that it's not possible to include directory extension based claims originating from Application Registrations. They did not go into detail but replied this:

I apologize to inform we got to know that reflecting extension claims in JWT token won't be possible. Since directory extensions can't be added to servicePrincipal objects.

I'm assuming the requesting entity is a service principal when using the client credentials flow.

Dan
  • 787
  • 1
  • 7
  • 20
  • Hi @Daniel. I have the use case as you, and I'm facing the same issue you did, and that's too bad to hear this was the conclusion. Could you please share with me what decision you have taken regarding that? Have you continued to use AAD apps for OAuth Client Credentials without a custom claim in the token, or you have followed another approach or used a different Service Provider? Thanks. – Alexandre Ribeiro Jan 17 '22 at 20:15
  • 1
    For now, we are using information stored in the app registrations manifest (directory extensions). During the authentication process, we are requesting these values via graph API, but imho this is far from ideal. This comes with an additional request and therefore a degraded performance. It would be way better if this data was stored inside the access token. Let me know if you come up with a better solution. – Dan Jan 18 '22 at 22:23
  • Hi, just wondering if you've made any progress with this solution, or you'll still calling the graph API? If so, what's the graph API endpoint for retrieving the info from the manifest? – user1784297 Sep 09 '22 at 12:48
  • I used to call the GraphApi endpoint `GET /applications/:id` [docs](https://learn.microsoft.com/en-us/graph/api/application-get). My data became more complex and the manifest wasn't sufficient anymore for storing it. So I moved my data to a CosmosDB and retrieve it during the authentication process. Eventually I did not manage to get the data inside a JWT token. I use the token for authentication only now. – Dan Sep 12 '22 at 18:15