OAuth2 Token Exchange — RFC 8693 Token Exchange for Impersonation and Delegation
In this tutorial, you will learn about OAuth2 Token Exchange. We cover key concepts, practical examples, and best practices to help you master this topic.
OAuth2 token exchange (RFC 8693) allows a client to exchange one access token for another with different scopes, audience, or impersonation context, enabling delegation patterns without exposing user credentials.
What You'll Learn
- Token exchange flow and grant type
- Impersonation and delegation scenarios
- Token translation between formats
- Scoping down tokens for sub-services
- Security considerations for token exchange
Why It Matters
Token exchange enables complex authorization patterns like service-to-service impersonation, token downgrading for Least Privilege, and federating tokens across trust boundaries. DodaTech uses token exchange to translate customer JWTs into internal service-specific tokens with minimal scope.
Real-World Use
An API Gateway receives a broad-scoped user JWT. Before forwarding to a sub-service, it exchanges the JWT for a narrowly-scoped token that only allows reading threat reports. The sub-service never sees the powerful original token.
sequenceDiagram
Client->>Gateway: Request with broad-scoped JWT
Gateway->>Auth Server: Token Exchange (broad JWT -> narrow JWT)
Auth Server->>Auth Server: Validate original, create new
Auth Server-->>Gateway: Narrow-scoped token (read:reports only)
Gateway->>Reports API: Request with narrow token
Reports API-->>Gateway: Response
Gateway-->>Client: Response
Code Examples
Example 1: Token Exchange Request
import requests
def exchange_token(original_token, target_audience, target_scopes):
"""Exchange a token for one with different audience/scope."""
url = 'https://auth.dodatech.com/token'
data = {
'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange',
'subject_token': original_token,
'subject_token_type': 'urn:ietf:params:oauth:token-type:access_token',
'audience': target_audience,
'scope': ' '.join(target_scopes)
}
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
response = requests.post(url, data=data, headers=headers)
return response.json()
# Exchange broad token for reports-specific token
broad_token = fetch_user_token()
narrow_token = exchange_token(
broad_token,
'https://reports.dodatech.com',
['read:reports']
)
print(f"New token scope: {narrow_token.get('scope')}")
print(f"Token type: {narrow_token.get('token_type')}")
# Output: New token scope: read:reports
# Output: Token type: Bearer
Example 2: Impersonation via Token Exchange
def impersonate_target(admin_token, target_user_id):
"""Exchange an admin token for a token impersonating a target user."""
url = 'https://auth.dodatech.com/token'
data = {
'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange',
'subject_token': admin_token,
'subject_token_type': 'urn:ietf:params:oauth:token-type:access_token',
'requested_token_type': 'urn:ietf:params:oauth:token-type:access_token',
'actor_token': admin_token,
'actor_token_type': 'urn:ietf:params:oauth:token-type:access_token',
'subject_token_claims': {
'sub': target_user_id,
'impersonator': extract_subject(admin_token)
}
}
response = requests.post(url, data=data)
result = response.json()
print(f"Impersonating: {target_user_id}")
print(f"Acting as: {extract_subject(admin_token)}")
return result['access_token']
# Admin fetches a token as a regular user
admin_token = get_admin_token()
user_token = impersonate_target(admin_token, 'user_456')
Example 3: Token Translation (JWT to OAuth2 Bearer)
def translate_jwt_to_opaque(jwt_token, client_id, client_secret):
"""Exchange a JWT for an opaque OAuth2 bearer token."""
url = 'https://auth.dodatech.com/token'
data = {
'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange',
'subject_token': jwt_token,
'subject_token_type': 'urn:ietf:params:oauth:token-type:jwt',
'requested_token_type': 'urn:ietf:params:oauth:token-type:access_token',
'client_id': client_id,
'client_secret': client_secret,
'scope': 'read write'
}
response = requests.post(url, data=data, auth=(client_id, client_secret))
result = response.json()
print(f"JWT -> Opaque token exchange complete")
print(f"Token type: {result.get('token_type')}")
print(f"Expires in: {result.get('expires_in')}s")
return result['access_token']
# Usage
opaque = translate_jwt_to_opaque(user_jwt, CLIENT_ID, CLIENT_SECRET)
Common Mistakes
1. Not Restricting Who Can Exchange Tokens
Only trusted services should have token exchange privileges. Use client credentials for authentication.
2. Issuing Tokens with Broader Scope
The resulting token must not have broader scope or longer expiry than the original token.
3. Ignoring Audience Claims
Always validate and set audience so the exchanged token is only usable for its intended service.
4. Infinite Exchange Chains
Prevent recursive exchange. Include a max_exchanges claim or limit exchange depth.
5. Not Logging Token Exchanges
Audit every token exchange including who performed it, what they exchanged, and why.
Practice Questions
- What grant type does token exchange use?
- How do you prevent token exchange from escalating privileges?
- What is the
actor_tokenused for? - When would you translate a JWT to an opaque token?
- How do you secure token exchange endpoints?
Answers:
urn:ietf:params:oauth:grant-type:token-exchange.- The authorization server must enforce that the exchanged token has equal or lesser scope and TTL.
- The
actor_tokenidentifies the entity performing the exchange, enabling audit trails for impersonation. - When internal services don't support JWT validation, or when you need to hide JWT claims from downstream services.
- Require client authentication (client_secret or private_key_jwt), rate limit exchanges, and audit all requests.
Challenge: Set up a token exchange service that supports scope downgrading and impersonation. Test that an exchanged token cannot be further exchanged (prevent infinite chains).
FAQ
What's Next
Combine token exchange with {{< ilink "OAuth" "OAuth2 Claims" }} for service-specific authorization, or explore {{< ilink "OAuth" "OAuth2 JWT" }} for structured token formats.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro