Skip to Content
GuidesAuthorizationToken Exchange

Token Exchange

Token Exchange (RFC 8693) allows a client to exchange an existing access token for a new one with a different subject, audience, or scope. This enables two key enterprise patterns: impersonation (acting as another user) and delegation (acting on behalf of a user with the original actor recorded).

Common use cases:

  • An admin impersonates a user to debug issues they are experiencing
  • A frontend service delegates its user token to a backend service with a restricted audience
  • A support agent acts on behalf of a customer with full audit trail via the act claim
  • A microservice narrows a broad token to a minimal-scope token for a downstream service

Impersonation vs Delegation

Token exchange supports two distinct patterns:

PatternSubject Changes?act ClaimUse Case
ImpersonationYes — new token’s sub is the target userContains the original actor’s identityAdmin debugging a user’s session
DelegationNo — sub remains the original userContains the delegating service’s identityService-to-service call preserving user context

Impersonation

The resulting token’s sub claim is replaced with the target user. The original actor is recorded in the act claim so the action is fully auditable:

{ "sub": "target-user-id", "iss": "https://auth.yourdomain.com", "type": "user", "act": { "sub": "admin-user-id", "email": "[email protected]" } }

Delegation

The sub claim stays the same (the original user), but an act claim records the intermediate service:

{ "sub": "original-user-id", "iss": "https://auth.yourdomain.com", "type": "user", "aud": "backend-service", "act": { "sub": "frontend-service-client-id" } }

Console Setup

Enable Token Exchange

In the Auris Console, go to Applications and select the application that will perform token exchanges. Under the Settings tab, toggle Enable Token Exchange to on.

Configure Allowed Exchange Types

Select which exchange types the application is permitted to perform:

TypeDescription
ImpersonationExchange a token for one with a different subject (requires impersonate:users permission)
DelegationExchange a token for one with a different audience (requires delegate:tokens permission)

Assign Permissions

Ensure the users or service accounts performing token exchange have the appropriate permissions:

  • impersonate:users — Required for impersonation exchanges
  • delegate:tokens — Required for delegation exchanges

These permissions can be assigned via roles in Console -> Roles -> [Role] -> Permissions.


Implementation

const AURIS_DOMAIN = 'https://auth.yourdomain.com' const CLIENT_ID = 'your-client-id' const CLIENT_SECRET = 'your-client-secret' // Impersonation: Admin acting as a specific user async function impersonateUser(adminToken, targetUserId) { const response = await fetch(`${AURIS_DOMAIN}/api/auth/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', subject_token: adminToken, subject_token_type: 'urn:ietf:params:oauth:token-type:access_token', requested_token_type: 'urn:ietf:params:oauth:token-type:access_token', requested_subject: targetUserId, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, }), }) if (!response.ok) { const error = await response.json() throw new Error(`Token exchange failed: ${error.error_description}`) } return await response.json() // { access_token: "...", token_type: "Bearer", expires_in: 3600 } } // Delegation: Frontend passing user context to backend async function delegateToBackend(userToken, backendAudience) { const response = await fetch(`${AURIS_DOMAIN}/api/auth/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', subject_token: userToken, subject_token_type: 'urn:ietf:params:oauth:token-type:access_token', requested_token_type: 'urn:ietf:params:oauth:token-type:access_token', audience: backendAudience, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, }), }) return await response.json() } // Usage const impersonatedToken = await impersonateUser(adminAccessToken, 'user-123') const delegatedToken = await delegateToBackend(userAccessToken, 'billing-service')

Request Parameters

ParameterRequiredDescription
grant_typeYesMust be urn:ietf:params:oauth:grant-type:token-exchange
subject_tokenYesThe existing access token to exchange
subject_token_typeYesMust be urn:ietf:params:oauth:token-type:access_token
requested_token_typeNoDefaults to urn:ietf:params:oauth:token-type:access_token
requested_subjectNoTarget user ID for impersonation. Omit for delegation.
audienceNoTarget audience for the new token. Used in delegation.
scopeNoRequested scope for the new token. Cannot exceed the original token’s scope.
client_idYesThe application’s client ID
client_secretYesThe application’s client secret

The act Claim

Token exchange always adds an act (actor) claim to the resulting token. This claim creates an auditable chain showing who actually performed the exchange:

Simple impersonation

{ "sub": "user-456", "act": { "sub": "admin-123" } }

Chained delegation

If a token that already has an act claim is exchanged again, the chain grows:

{ "sub": "user-456", "act": { "sub": "service-b", "act": { "sub": "service-a", "act": { "sub": "admin-123" } } } }

This chain provides a complete audit trail of every service and user involved in the token exchange sequence.


Validating Exchanged Tokens

When your API receives a token with an act claim, you can inspect it to understand the delegation chain:

import { verifyToken } from '@auris/js/jwt-verify' async function handleRequest(req) { const payload = await verifyToken(req.headers.authorization.slice(7), { jwksUrl: 'https://auth.yourdomain.com/.well-known/jwks.json', }) // Check if this is an impersonated or delegated token if (payload.act) { console.log(`Action performed by ${payload.act.sub} acting as ${payload.sub}`) // You may want to log or restrict certain operations for impersonated tokens if (isDestructiveOperation(req)) { throw new Error('Destructive operations are not allowed via impersonated tokens') } } }

Security Considerations

  • Permission enforcement: Impersonation requires impersonate:users and delegation requires delegate:tokens. These are sensitive permissions that should be granted sparingly.
  • Audit logging: Every token exchange is logged with the original actor, target subject, exchange type, and timestamp. These logs are visible in the Auris Console under Logs.
  • Scope restriction: Exchanged tokens cannot have broader scope than the original token. You can only narrow scope, never expand it.
  • Confidential client required: Token exchange requires client authentication. Public clients cannot perform exchanges.
  • Token lifetime: Exchanged tokens have a shorter default lifetime (1 hour) and cannot exceed the remaining lifetime of the original token.

Impersonation is a powerful capability. Only assign the impersonate:users permission to trusted administrator roles. Consider adding additional controls in your application layer, such as blocking impersonation for destructive operations or requiring a reason/ticket number.


API Endpoints

POST/api/auth/token

Token endpoint. For token exchange, set grant_type=urn:ietf:params:oauth:grant-type:token-exchange with the parameters described above. Requires client authentication.

GET/api/oauth/token-exchangesRequires: view:token_exchanges

List recent token exchange events. Filterable by user, type (impersonation/delegation), and date range.


Required Permissions

OperationPermission
Perform impersonation exchangeimpersonate:users
Perform delegation exchangedelegate:tokens
View token exchange historyview:token_exchanges
Enable Token Exchange on an applicationmanage:applications