Token Exchange: Delegation Is Not Impersonation (RFC 8693)

When a service calls another service for a user, the token it carries decides whether your audit log says who acted. RFC 8693 makes that a design choice.


Token exchange: a service trades a user's token at the authorization server for a downstream token; impersonation returns a token that names only the user, delegation returns one carrying an act claim that names the acting service alongside the subject

Something goes wrong in a downstream system and you go to the audit log to find out who did it. The log says the user did it. The user did not do it — a service did, on the user’s behalf, three hops away. Whether your log can tell the difference was decided long before the incident, by which token that service was carrying.

The problem in one paragraph

A user calls service A. Service A needs to call service B to finish the job. What token should A present to B?

Three options get used in practice, and two are bad.

Pass the user’s token straight through. Simple, and it works until it doesn’t. B now holds a credential minted for A, with A’s audience and A’s scopes, and every service in the chain accumulates a token that is more powerful than its job requires. The audit trail at B records the user. It has no idea A was involved.

Use a service account. Now B knows exactly which service called — and has completely lost the user. Your authorization decisions at B can no longer depend on who the work is for, so they usually degrade into “service A is allowed to read everything,” which is how one compromised service becomes a full data breach.

Exchange the token. A presents what it has to the authorization server and asks for a token appropriate to calling B. This is what RFC 8693 standardizes, and its real contribution is not the plumbing — it is forcing you to state which of two very different things you meant.

The exchange request

Token exchange is an OAuth grant type, so it is a form POST to the token endpoint like any other:

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
subject_token=<the token representing who the work is for>
subject_token_type=urn:ietf:params:oauth:token-type:access_token
audience=https://service-b.internal
scope=reports:read

subject_token and subject_token_type are required — the subject is the party on whose behalf the request is made. The optional resource, audience and scope parameters say what the new token is for, which is how you narrow authority on the way down a call chain rather than widening it. requested_token_type asks for a particular output format. Token types are named by URN, including urn:ietf:params:oauth:token-type:access_token, :refresh_token, :id_token, :jwt, and the two SAML variants.

That request, as written above, asks for impersonation. One more parameter changes it.

Impersonation versus delegation

This is the distinction the whole RFC exists to make, and it is worth stating precisely.

Impersonation. The caller is given the rights of the subject and becomes indistinguishable from them in that context. Downstream, there is one identity: the user. Nothing in the token records that a service was in the middle.

Delegation. The caller keeps its own identity, separate from the subject’s, and it is explicitly understood that this party is acting as an agent for the other. Downstream, there are two identities: who it is for, and who is doing it.

The request shape selects between them. A subject_token on its own asks for impersonation. Adding an actor_token — plus actor_token_type, which becomes required once an actor token is present — identifies the acting party and asks for delegation.

The difference shows up where it matters most: after something has gone wrong. Under impersonation the downstream log cannot distinguish the user opening a report from a compromised middle service reading every report. Under delegation it can, because the token said so.

How delegation is written into the token: act and may_act

Two JWT claims carry this.

act records the current actor. Its value is a JSON object whose members identify the party acting on the subject’s behalf — so the token’s sub remains the user while act names the service. Because the value is an object, it can itself contain an act claim, and that nesting expresses a chain: B acted for A, which acted for the user. The token carries the whole path, not just the last hop.

may_act is the permission to do this, stated ahead of time. It says a named party is authorized to become the actor for this subject. This matters because otherwise any service holding a valid token could ask to act for anyone; may_act lets the authorization server check a requested delegation against policy and refuse the ones nobody sanctioned.

Read together: may_act is the grant, act is the receipt.

Why this stopped being an enterprise-SSO footnote

Token exchange sat in the background for years as something large SSO deployments did between SAML and OAuth worlds. Agents dragged it back to the centre, because an agent doing work for a person is delegation in its purest form — and the industry’s default shortcut is the worst of the three options above.

Hand an agent a shared API key and you have erased both identities in one move. The logs show a service account touching data. Which agent ran? For which user? Under what grant? Nothing in the request answers those, which is the argument for giving agents real identities instead of shared keys and, one level up, why agent authorization is turning into an audit-trail problem rather than an access-control one.

Delegation-style exchange gives you the shape you actually want at each hop: the user as sub, the agent as act, and a scope narrowed to the immediate task. It composes with the flow most interactive apps already run — Authorization Code with PKCE gets you the first token; exchange is what you do with it afterwards.

Practical notes

Narrow on every hop. The point of specifying audience and scope is that authority should shrink as it travels. If the token B receives can do everything the token A received could, you have built pass-through with extra steps.

Keep the exchanged tokens short-lived. A delegated token is a standing statement that one party may act for another; that statement should expire quickly. Short lifetimes remain the practical revocation story for anything self-contained.

Log the actor, not just the subject. Emitting a delegated token and then recording only sub in your logs throws away the thing you paid for. The audit record should name both.

Support varies. This is an authorization-server capability, not a library you drop into a service. Check what your identity provider actually implements — several support the grant but not actor_token, which means you get impersonation under a delegation-shaped name. That is exactly the case worth catching in a test rather than in an incident.

The rule worth remembering

Impersonation answers “what may be done”; delegation also answers “by whom, for whom.” RFC 8693 makes that a parameter rather than an accident: subject_token alone erases the caller, actor_token preserves it, act records the chain and may_act authorizes it in advance. Pick deliberately — and remember that the audit log you will read during an incident was configured by the token you issued long before it.

Frequently asked questions

What is OAuth token exchange (RFC 8693)?

RFC 8693 defines a standard way for a client to trade one security token for another at the authorization server, using the grant type urn:ietf:params:oauth:grant-type:token-exchange. It exists for the case where a service holds a token for one context and needs a token for a different one — a different audience, a narrower scope, or a different token format. The incoming token goes in the subject_token parameter with its type in subject_token_type, and the server returns a new token scoped to what was asked for via the optional resource, audience and scope parameters.

What is the difference between impersonation and delegation?

Under impersonation, the calling party is given the rights of the user and becomes indistinguishable from them in that context — downstream services see only the user, and the audit trail cannot tell you which service actually made the call. Under delegation, the caller keeps its own identity alongside the user's, and it is explicitly recorded that this party is acting as an agent for that user. In RFC 8693 the request shape decides which you get: a subject_token alone yields impersonation, while including an actor_token asks for delegation.

What do the act and may_act claims do?

The act claim records the current actor in a delegation: its value is a JSON object whose members identify the party acting on the subject's behalf. Nesting act claims inside one another expresses a chain of delegation, so a token can carry the whole path rather than just the last hop. The may_act claim is the authorization side of the same idea — it states in advance that a named party is permitted to become the actor for this subject, which lets the authorization server check a delegation against policy instead of accepting any caller that presents a valid token.

Why does token exchange matter for AI agents?

An agent doing work for a person is the textbook delegation case, and the common shortcut — giving the agent a shared service credential — erases both identities at once. Afterwards the logs show a service account touching data, with no record of which agent ran or which user it was acting for. Delegation-style exchange keeps both halves: the agent's own identity as the actor, the user as the subject, and a scope narrowed to the task. That is also what makes after-the-fact questions answerable, which is increasingly what auditors and regulators are asking for.

Comments