Is your feature request or bug related to a problem? Please describe.
I followed Microsoft's recommended pattern for an agent identity calling a non-Graph downstream API:
- The downstream is its own Entra app registration with
requestedAccessTokenVersion = 2 and identifierUris = ["api://<custom-api-client-id>"].
- The Microsoft Entra Auth SDK sidecar's
DownstreamApis config requests api://<custom-api-client-id>/.default for that downstream.
- The custom API validates inbound tokens with standard JWKS + RS256 + issuer + audience + expiry.
using a customized sidecar dev sample based on https://github.com/microsoft/entra-agentid-samples/tree/main/sidecar/dev,
The auth chain works and the audience check passes. But the resulting agent identity token (TR) lacks every agent-identity-specific claim that the agent token claims reference describes as core to agent tokens.
For the same agent identity in the same blueprint, the two-hop autonomous flow with different target scopes returns very different token shapes:
TR with scope=https://graph.microsoft.com/.default leads to agent-identity claims being present:
{
"aud": "00000003-0000-0000-c000-000000000000",
"azp": "<agent-identity-client-id>",
"idtyp": "app",
"xms_act_fct": "9 3 11",
"xms_idrel": "7 10",
"xms_sub_fct": "9 3 11",
"xms_par_app_azp": "<blueprint-client-id>",
"xms_tnt_fct": "3 9",
"ver": "1.0",
...
}
TR with scope=api://<custom-api-client-id>/.default leads to agent-identity claims absent:
{
"aud": "<custom-api-client-id>",
"azp": "<agent-identity-client-id>",
"azpacr": "2",
"iss": "https://login.microsoftonline.com/<tid>/v2.0",
"oid": "<agent-identity-oid>",
"sub": "<agent-identity-oid>",
"ver": "2.0",
"xms_ftd": "<opaque>"
// No idtyp, no xms_act_fct, no xms_idrel, no xms_sub_fct, no xms_par_app_azp
}
This'd mean that the protected API cannot distinguish "agent identity caller" from "regular service-principal caller" because the xms_* agent-id facets and idtyp are missing. It also cannot record the parent blueprint app ID for audit, which the docs explicitly recommend:
"Log the parent application ID for auditing purposes. Microsoft Entra ID sign-in logs always includes the parent ID if available, so resource server should do the same." — agent-token-claims#xms_par_app_azp
Resource servers can only log claims they're given. So either I am doing something wrong (which I expect) or, for custom audiences, they aren't given currently.
Describe the solution you'd like
One of:
-
Documentation: If this behaviour is by-design (e.g. xms_* claims are Microsoft-internal extensions emitted only for Microsoft-owned audiences), the agent token claims reference and call a custom API docs would be much clearer if they said so. The current wording reads as if these claims are there always with agent identity tokens.
-
Configuration: If a setting can opt these claims into custom-audience tokens (e.g. via optionalClaims.accessToken on the protected API's app reg, or on the blueprint / agent identity), please document the exact configuration shape. We've not found any existing doc that addresses the audience-dependent emission behaviour.
-
Always-emit. Ideally, the agent-identity claims travel with the token regardless of audience, so any protected API can do governance / audit on agent context.
Describe alternatives you've considered
- Audience-side validation only (what I currently do): authorize on
azp matching expected agent identity OIDs in an allowlist on the API. Works, but loses the "is this an agent vs a regular SP" distinction and the parent-blueprint trace.
- Calling Microsoft Graph in addition to our API: not viable — adds a hop and a perm, doesn't solve the problem at the protected-API boundary. nope.
- Reading the agent identity from
azp and looking up its blueprint membership via Graph at validation time: also not viable. a workaround that adds latency and a Graph dependency on every request. nope.
Additional context
The investigation came out of a PoC building an autonomous agent calling a custom downstream API. The TR is acquired via the documented two-hop autonomous-agent flow:
The PoC is based on https://github.com/microsoft/entra-agentid-samples/tree/main/sidecar/dev using the sidecar, but I wanted audience validation to do it right here, not confused deputy-style as in the reference repo.
- POST
/oauth2/v2.0/token with the blueprint's client_credentials + fmi_path=<agent-identity-id> → T1
- POST
/oauth2/v2.0/token with the agent identity's client_id + client_assertion=T1 → TR
Both hops use the same tid and identity material; only the requested scope differs between the Graph-test and custom-API-test invocations.
Are you using a language adapter to call the sidecar?
No. The agent calls the Microsoft Entra Auth SDK sidecar directly over HTTP (Python requests to http://sidecar:5000/AuthorizationHeaderUnauthenticated/<downstream-key>?AgentIdentity=<agent-id>).
Can you provide a repro of the issue?
Yes. The minimal repro is the two-hop client_credentials flow against any single-tenant blueprint + agent identity, varying only the scope between Graph and a custom API:
# Hop 1: blueprint client_credentials + fmi_path → T1
curl -s -X POST "https://login.microsoftonline.com/<tid>/oauth2/v2.0/token" \
--data-urlencode "client_id=<blueprint-client-id>" \
--data-urlencode "scope=api://AzureADTokenExchange/.default" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_secret=<blueprint-secret>" \
--data-urlencode "fmi_path=<agent-identity-id>"
# Hop 2 (Graph scope) — TR carries idtyp, xms_act_fct, xms_par_app_azp, ...
curl -s -X POST "https://login.microsoftonline.com/<tid>/oauth2/v2.0/token" \
--data-urlencode "client_id=<agent-identity-client-id>" \
--data-urlencode "scope=https://graph.microsoft.com/.default" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
--data-urlencode "client_assertion=$T1"
# Hop 2 (custom-API scope) — TR carries azp + ver=2.0 + xms_ftd; no agent claims
curl -s -X POST "https://login.microsoftonline.com/<tid>/oauth2/v2.0/token" \
--data-urlencode "client_id=<agent-identity-client-id>" \
--data-urlencode "scope=api://<custom-api-client-id>/.default" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
--data-urlencode "client_assertion=$T1"
The custom-API app registration is a vanilla azuread_application with requestedAccessTokenVersion = 2, one app_role (e.g. Mailbox.Read.Demo), one oauth2_permission_scope (e.g. Mailbox.Read.User.Demo), and an azuread_application_identifier_uri set to api://<client-id>. No optionalClaims configured. Happy to share a redacted Terraform definition or a full token decode if useful.
Is your feature request or bug related to a problem? Please describe.
I followed Microsoft's recommended pattern for an agent identity calling a non-Graph downstream API:
requestedAccessTokenVersion = 2andidentifierUris = ["api://<custom-api-client-id>"].DownstreamApisconfig requestsapi://<custom-api-client-id>/.defaultfor that downstream.using a customized sidecar dev sample based on https://github.com/microsoft/entra-agentid-samples/tree/main/sidecar/dev,
The auth chain works and the audience check passes. But the resulting agent identity token (TR) lacks every agent-identity-specific claim that the agent token claims reference describes as core to agent tokens.
For the same agent identity in the same blueprint, the two-hop autonomous flow with different target scopes returns very different token shapes:
TR with
scope=https://graph.microsoft.com/.defaultleads to agent-identity claims being present:{ "aud": "00000003-0000-0000-c000-000000000000", "azp": "<agent-identity-client-id>", "idtyp": "app", "xms_act_fct": "9 3 11", "xms_idrel": "7 10", "xms_sub_fct": "9 3 11", "xms_par_app_azp": "<blueprint-client-id>", "xms_tnt_fct": "3 9", "ver": "1.0", ... }TR with
scope=api://<custom-api-client-id>/.defaultleads to agent-identity claims absent:{ "aud": "<custom-api-client-id>", "azp": "<agent-identity-client-id>", "azpacr": "2", "iss": "https://login.microsoftonline.com/<tid>/v2.0", "oid": "<agent-identity-oid>", "sub": "<agent-identity-oid>", "ver": "2.0", "xms_ftd": "<opaque>" // No idtyp, no xms_act_fct, no xms_idrel, no xms_sub_fct, no xms_par_app_azp }This'd mean that the protected API cannot distinguish "agent identity caller" from "regular service-principal caller" because the
xms_*agent-id facets andidtypare missing. It also cannot record the parent blueprint app ID for audit, which the docs explicitly recommend:Resource servers can only log claims they're given. So either I am doing something wrong (which I expect) or, for custom audiences, they aren't given currently.
Describe the solution you'd like
One of:
Documentation: If this behaviour is by-design (e.g.
xms_*claims are Microsoft-internal extensions emitted only for Microsoft-owned audiences), the agent token claims reference and call a custom API docs would be much clearer if they said so. The current wording reads as if these claims are there always with agent identity tokens.Configuration: If a setting can opt these claims into custom-audience tokens (e.g. via
optionalClaims.accessTokenon the protected API's app reg, or on the blueprint / agent identity), please document the exact configuration shape. We've not found any existing doc that addresses the audience-dependent emission behaviour.Always-emit. Ideally, the agent-identity claims travel with the token regardless of audience, so any protected API can do governance / audit on agent context.
Describe alternatives you've considered
azpmatching expected agent identity OIDs in an allowlist on the API. Works, but loses the "is this an agent vs a regular SP" distinction and the parent-blueprint trace.azpand looking up its blueprint membership via Graph at validation time: also not viable. a workaround that adds latency and a Graph dependency on every request. nope.Additional context
The investigation came out of a PoC building an autonomous agent calling a custom downstream API. The TR is acquired via the documented two-hop autonomous-agent flow:
The PoC is based on https://github.com/microsoft/entra-agentid-samples/tree/main/sidecar/dev using the sidecar, but I wanted audience validation to do it right here, not confused deputy-style as in the reference repo.
/oauth2/v2.0/tokenwith the blueprint'sclient_credentials+fmi_path=<agent-identity-id>→ T1/oauth2/v2.0/tokenwith the agent identity'sclient_id+client_assertion=T1→ TRBoth hops use the same
tidand identity material; only the requested scope differs between the Graph-test and custom-API-test invocations.Are you using a language adapter to call the sidecar?
No. The agent calls the Microsoft Entra Auth SDK sidecar directly over HTTP (Python
requeststohttp://sidecar:5000/AuthorizationHeaderUnauthenticated/<downstream-key>?AgentIdentity=<agent-id>).Can you provide a repro of the issue?
Yes. The minimal repro is the two-hop
client_credentialsflow against any single-tenant blueprint + agent identity, varying only thescopebetween Graph and a custom API:The custom-API app registration is a vanilla
azuread_applicationwithrequestedAccessTokenVersion = 2, oneapp_role(e.g.Mailbox.Read.Demo), oneoauth2_permission_scope(e.g.Mailbox.Read.User.Demo), and anazuread_application_identifier_uriset toapi://<client-id>. NooptionalClaimsconfigured. Happy to share a redacted Terraform definition or a full token decode if useful.