Token exchange and delegation

OAuth 2.0 Course · lesson 13 of 13 · 5 min read

Carrying user identity through a call chain without over-granting.

Open this lesson in the learning hub

Key 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 act claim 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.