โ 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 AA backend the user's app talks to๐
Login serverChecks who you are and hands out tokens๐ฆ
Orders APIA second service Service A needsUser 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
subso 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 }
}