Token exchange and delegation
Carrying user identity through a call chain without over-granting.
Open this lesson in the learning hubKey points
- Forwarding the original user token down a chain is simple and over-grants: every hop receives the user full scope, so a compromise anywhere is a compromise everywhere.
- Token exchange (RFC 8693) swaps a token for a new one that is narrower - scoped to the next audience, with reduced scopes, and still carrying the user identity.
- The
actclaim records the delegation chain, so the downstream service can see both who the user is and which service is acting for them. - That distinction matters for auditing: "service B did X on behalf of user U" is a different fact from "user U did X", and only one of them is true.
- The cost is a call to the identity provider per hop, which is why exchanged tokens should be cached for their lifetime rather than fetched per request.
- Where a chain is deep, consider whether the downstream call needs user identity at all. A service that only aggregates public data may need no user context.
Example
# Exchange a user token for one scoped to the next service.
$ curl -X POST https://auth.example.com/oauth/token \
-d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
-d subject_token=$USER_TOKEN \
-d subject_token_type=urn:ietf:params:oauth:token-type:access_token \
-d audience=inventory-api \
-d scope=inventory.read \
-d client_id=orders-service -d client_secret=$SECRET
# The result carries the user AND the acting service:
{
"sub": "user-123", # still the user
"aud": "inventory-api", # narrowed to ONE audience
"scope": "inventory.read", # narrowed scopes
"act": { "sub": "orders-service" } # who is acting on their behalf
}
---
# FORWARDING vs EXCHANGE:
#
# FORWARD the original token
# gateway -> orders -> inventory -> pricing
# every hop holds a token with the user FULL scope
# compromise pricing -> attacker can call anything as that user
#
# EXCHANGE at each hop
# each token: one audience, minimum scopes, identity preserved
# compromise pricing -> attacker holds a token for pricing only
#
# The cost: one IdP round trip per hop. Cache per (user, audience) for
# the token lifetime, or the identity provider becomes the bottleneck.
Exchange rather than forward - each hop gets a token narrowed to one audience while the user identity and the acting service both survive.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the OAuth 2.0 Course course, and every lesson in it is listed on the OAuth 2.0 Course contents page.