cd /news/ai-agents/agentgateway-adds-token-exchange-jwt… · home topics ai-agents article
[ARTICLE · art-57287] src=agentgateway.dev ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Agentgateway adds token exchange, JWT-assertion, and Entra OBO

Agentgateway has open-sourced three new token exchange capabilities—RFC 8693 token exchange, RFC 7523 JWT assertion, and Microsoft Entra on-behalf-of—enabling AI agents to call downstream APIs as the user without custom code. The features are part of the upcoming agentgateway release under the backendAuth.oauthTokenExchange configuration block.

read5 min views1 publishedJul 13, 2026
Agentgateway adds token exchange, JWT-assertion, and Entra OBO
Image: source

Native backend auth for RFC 8693 token exchange, RFC 7523 JWT bearer (jwt-assertion), and Microsoft Entra on-behalf-of — so agents can call downstream APIs as the user without hand-rolled ext_proc or custom agent code

Back to blog

Shielding AI agents from sensitive MCP / API credentials (secrets, API keys, tokens, etc) is the prevailing best practice pattern to keep sensitive secrets from leaking into AI model conversations or across AI agent boundaries. OpenClaw, for example, has direct file system access in its most general deployment and can read files/secrets/env variables, etc and could easily send these secrets in any requests.

The right way in an enterprise environment: user identity + agent identity tied to an authorization grant, which determines what is allowed to be done. Any calls to MCP servers or APIs have their credentials injected transparently by the infrastructure. The cornerstone of this injection is exchanging an agent or user’s identity for the correct secrets/API keys/tokens.

In the upcoming agentgateway release, we have opensourced two new exchange capabilities, including a bonus third exchange opportunity with Microsoft Entra — all under backendAuth.oauthTokenExchange

:

Grant Spec Subject sent as Typical IdP
Token exchange

subject_token

RFC 7523assertion

assertion

Entra is the important enterprise wrinkle: it does not speak RFC 8693. Its OBO flow is jwt-bearer with requested_token_use=on_behalf_of

. Same outcome (user-scoped downstream token), different grant shape — so both grants live under one oauthTokenExchange

block.

RFC 8693 token exchange #

Default grant. The gateway POSTs the inbound bearer as subject_token

to your IdP’s token endpoint and attaches the returned access token upstream.

backendAuth:
  oauthTokenExchange:
    host: idp.example.com:443
    tokenEndpointPath: /oauth2/default/v1/token
    clientAuth:
      clientId: gateway-client
      clientSecret: $OAUTH_CLIENT_SECRET
      method: clientSecretBasic
    audiences:
    - upstream-api
    scopes:
    - read

What the gateway sends (conceptually):

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
subject_token=<inbound bearer>
subject_token_type=urn:ietf:params:oauth:token-type:access_token
audience=upstream-api
scope=read

Optional knobs that matter in agentic setups:

— RFC 8693 delegation (actorToken

act

claim). Useful when an agent identity is actingfora user.—resources

RFC 8707resource indicators when the AS expects them (common for MCP / API audiences).— read the subject from a non-default header, cookie, or CEL expression instead ofsubjectToken.source

Authorization

.— in-memory cache keyed by subject + grant params; TTL capped by the subject JWTcache

exp

.

Runnable example: examples/traffic-token-exchange/oauth-rfc8693.

JWT assertion (RFC 7523 jwt-bearer) #

Flip grantType: jwtBearer

and the same policy speaks JWT bearer instead of token exchange. The inbound credential becomes the assertion

(not subject_token

). requestedTokenType

/ actorToken

are rejected for this grant — they belong to RFC 8693.

backendAuth:
  oauthTokenExchange:
    host: idp.example.com:443
    tokenEndpointPath: /realms/backend-oauth/protocol/openid-connect/token
    grantType: jwtBearer
    clientAuth:
      clientId: requester-client
      clientSecret: requester-secret
      method: clientSecretBasic
    audiences:
    - target-client

Form shape:

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
assertion=<inbound token>
audience=target-client

This grant works for any authorization servers that support JWT Assertion Grant (RFC 7523). This is the grant Keycloak’s JWT Authorization Grant (preview in 26.5+) implements across realms — present a JWT from realm A, get a token from realm B that trusts A. Full two-realm walkthrough: examples/traffic-token-exchange/jwt-authz-grant.

Microsoft Entra OBO #

Entra OBO is jwt-bearer with provider-specific extras. The hand-written request looks like:

curl -X POST "https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=<CLIENT_ID>" \
  -d "client_secret=<CLIENT_SECRET>" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
  -d "requested_token_use=on_behalf_of" \
  -d "scope=https://graph.microsoft.com/.default" \
  -d "assertion=<USER_ACCESS_TOKEN>"

Agentgateway produces that same request from config:

backendAuth:
  oauthTokenExchange:
    host: login.microsoftonline.com:443
    tokenEndpointPath: /<TENANT_ID>/oauth2/v2.0/token
    grantType: jwtBearer
    clientAuth:
      clientId: <CLIENT_ID>
      clientSecret: <CLIENT_SECRET>
      method: clientSecretPost   # credentials in the BODY (Entra expects this shape)
    scopes:
    - https://graph.microsoft.com/.default
    additionalParams:
      requested_token_use: '"on_behalf_of"'   # CEL string literal
Microsoft form field Produced by
grant_type=...jwt-bearer grantType: jwtBearer
assertion=<user token> inbound bearer (jwt-bearer sends subject as assertion )
scope=... scopes: (joined into one space-delimited scope )
client_id + client_secret in body clientAuth.method: clientSecretPost
requested_token_use=on_behalf_of additionalParams (CEL)

Gotchas worth calling out:

requested_token_use

is a vendor extension, so it lives inadditionalParams

, not a first-class field. Values areCEL expressions— a literal string needs the inner quotes:'"on_behalf_of"'

.- Prefer clientSecretPost

for Entra so credentials land in the form body (matching Microsoft’s docs). DefaultclientSecretBasic

would put them in anAuthorization: Basic

header instead. - Inbound JWT validation still belongs on the route(jwtAuth

/ MCP auth). Exchange only runs after the gateway has accepted the user.

This is the missing piece after enterprise MCP SSO with Entra: SSO gets the user into the gateway; OBO gets the user out to Graph (or any Entra-protected API / MCP server) without re-prompting login.

Kubernetes: same knobs as a policy #

On Kubernetes the same exchange attaches via AgentgatewayPolicy

on a Service or Backend:

apiVersion: agentgateway.dev/v1alpha1
kind: AgentgatewayPolicy
metadata:
  name: okta-token-exchange
spec:
  targetRefs:
  - group: ""
    kind: Service
    name: my-backend
  backend:
    auth:
      oauthTokenExchange:
        tokenEndpoint:
          group: agentgateway.dev
          kind: AgentgatewayBackend
          name: okta-token-endpoint
          port: 443
          path: /oauth2/default/v1/token
        clientAuth:
          clientId: "<okta-client-id>"
          secretRef:
            name: okta-oauth-client
        scopes:
        - "<scope>"

Token endpoints are backend references (not raw URLs), so TLS, DNS, and ReferenceGrant rules stay consistent with the rest of the data plane. Client secrets come from Kubernetes Secrets; privateKeyJwt

is available when your AS (Okta, etc.) requires signed client assertions.

Beyond single-leg exchange #

If every MCP tool call just reused the inbound enterprise JWT, every upstream would see a token that was never minted for it. If every agent SDK keeps its own token vault, you get N credential stores and zero consistent audit which is exactly the OpenClaw-style leak surface from the earlier in this post. Gateway-side exchange keeps secrets out of the agent, preserves user identity (and optional actorToken

for agent-on-behalf-of-user), and caches so agent loops don’t hammer the token endpoint.

These single-leg grants are also the building blocks for Cross App Access / ID-JAG (a two-leg composition of token exchange and JWT-bearer). See the traffic-cross-app-access examples if you want to explore that path. Will cover that in depth in future blogs.

Try it #

── more in #ai-agents 4 stories · sorted by recency
── more on @agentgateway 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/agentgateway-adds-to…] indexed:0 read:5min 2026-07-13 ·