Skip to content

[Entra ID sidecar] Agent identity tokens seem to drop xms_act_fct / idtyp / xms_par_app_azp claims for custom-API audiences (vs Microsoft Graph). Is this expected, configurable, or a bug? #3882

Description

@DGuhr

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:

  1. The downstream is its own Entra app registration with requestedAccessTokenVersion = 2 and identifierUris = ["api://<custom-api-client-id>"].
  2. The Microsoft Entra Auth SDK sidecar's DownstreamApis config requests api://<custom-api-client-id>/.default for that downstream.
  3. 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.

  1. POST /oauth2/v2.0/token with the blueprint's client_credentials + fmi_path=<agent-identity-id> → T1
  2. 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.

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationsidecarIssues, bugs, feature requests for the EntraID sidecar

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions