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
actclaim - A microservice narrows a broad token to a minimal-scope token for a downstream service
Impersonation vs Delegation
Token exchange supports two distinct patterns:
| Pattern | Subject Changes? | act Claim | Use Case |
|---|---|---|---|
| Impersonation | Yes — new token’s sub is the target user | Contains the original actor’s identity | Admin debugging a user’s session |
| Delegation | No — sub remains the original user | Contains the delegating service’s identity | Service-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:
| Type | Description |
|---|---|
| Impersonation | Exchange a token for one with a different subject (requires impersonate:users permission) |
| Delegation | Exchange 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 exchangesdelegate:tokens— Required for delegation exchanges
These permissions can be assigned via roles in Console -> Roles -> [Role] -> Permissions.
Implementation
JavaScript
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
| Parameter | Required | Description |
|---|---|---|
grant_type | Yes | Must be urn:ietf:params:oauth:grant-type:token-exchange |
subject_token | Yes | The existing access token to exchange |
subject_token_type | Yes | Must be urn:ietf:params:oauth:token-type:access_token |
requested_token_type | No | Defaults to urn:ietf:params:oauth:token-type:access_token |
requested_subject | No | Target user ID for impersonation. Omit for delegation. |
audience | No | Target audience for the new token. Used in delegation. |
scope | No | Requested scope for the new token. Cannot exceed the original token’s scope. |
client_id | Yes | The application’s client ID |
client_secret | Yes | The 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:usersand delegation requiresdelegate: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
/api/auth/tokenToken endpoint. For token exchange, set grant_type=urn:ietf:params:oauth:grant-type:token-exchange with the parameters described above. Requires client authentication.
/api/oauth/token-exchangesRequires: view:token_exchangesList recent token exchange events. Filterable by user, type (impersonation/delegation), and date range.
Required Permissions
| Operation | Permission |
|---|---|
| Perform impersonation exchange | impersonate:users |
| Perform delegation exchange | delegate:tokens |
| View token exchange history | view:token_exchanges |
| Enable Token Exchange on an application | manage:applications |
Related Guides
- M2M Client Credentials — Server-to-server authentication without token exchange
- Custom JWT Claims — Adding custom claims to access tokens
- Roles & Permissions — Managing the permissions required for token exchange