Skip to content

OAuth2 Token Exchange — RFC 8693 Token Exchange for Impersonation and Delegation

DodaTech Updated 2026-06-28 4 min read

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

  1. What grant type does token exchange use?
  2. How do you prevent token exchange from escalating privileges?
  3. What is the actor_token used for?
  4. When would you translate a JWT to an opaque token?
  5. How do you secure token exchange endpoints?

Answers:

  1. urn:ietf:params:oauth:grant-type:token-exchange.
  2. The authorization server must enforce that the exchanged token has equal or lesser scope and TTL.
  3. The actor_token identifies the entity performing the exchange, enabling audit trails for impersonation.
  4. When internal services don't support JWT validation, or when you need to hide JWT claims from downstream services.
  5. 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

Is token exchange part of core OAuth2?

: No, it's defined in RFC 8693 as an extension. Not all authorization servers support it.

Can I exchange a refresh token for an access token?

: That's the standard refresh flow, not token exchange. Token exchange swaps access tokens for other access tokens.

Does token exchange work across different auth servers?

: Yes, with federation. One auth server trusts tokens from another and issues its own.

How do I revoke exchanged tokens?

: Each exchanged token is an independent access token. Revoke it using the standard revocation endpoint.

What is the `requested_token_type` parameter?

: It specifies what kind of token you want back (access_token, jwt, id_token, etc.).

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