โ† All flows

Token Exchange

Swap one token for another that is made for the next service

Token Exchange lets a service trade a token it already holds for a new token suited to a different job. The most common case is a chain of services. A user calls Service A, and Service A needs to call Service B for that same user.

  • The token you hold is the subject_token.
  • The API you want to reach is the audience.
  • The new token can be narrower (fewer scopes) but should never be broader.

See it step by step

โš™๏ธ
Service A
๐Ÿ”
Login server
๐Ÿ“ฆ
Orders API
User token

Step 1 of 5

Service A holds a user's token

The token was issued for Service A only. The Orders API would reject it, because it is not the intended audience.

When to use this flow

  • Microservices โ€” Pass the user's identity down a chain of calls
  • Narrowing access โ€” Give a downstream service only the scopes it needs
  • Crossing domains โ€” Turn a token from one system into one another system accepts
  • Forwarding the same token everywhere โ€” Exchange it so each service gets its own audience

Good practices

  • Authenticate the calling service โ€” The exchange request is a client request like any other
  • Ask for a specific audience โ€” Name the one API the new token is for
  • Request only the scopes you need โ€” Narrow them at every hop
  • Keep exchanged tokens short-lived โ€” Exchange again instead of storing them

Common mistakes

  • Passing the user's token straight through โ€” It is issued for another audience and widens exposure
  • Exchanging with no policy โ€” Decide which clients may reach which audiences
  • Expecting more access โ€” The new token cannot exceed what the original allowed
  • Losing track of the user โ€” Keep the original sub so audit logs show who the call is for
Code example
Token Exchange example
javascript
// Token Exchange (RFC 8693), from Service A
async function tokenForOrdersApi(subjectToken) {
  const res = await fetch('https://auth.provider.com/oauth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      Authorization: 'Basic ' + btoa(CLIENT_ID + ':' + CLIENT_SECRET),
    },
    body: new URLSearchParams({
      grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
      subject_token: subjectToken,
      subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
      audience: 'https://orders.example.com',
      scope: 'orders.read',
    }),
  });
  if (!res.ok) throw new Error('Exchange refused');
  return res.json(); // { access_token, issued_token_type, token_type, expires_in }
}