Token Exchange
Token Exchange (RFC 8693) lets a client present an existing token to ThunderID and request a different token in return. The exchanged token can be bound to a target resource server, carry narrower scopes, or represent an "on-behalf-of" act in a service-to-service call chain.
Typical uses:
- A frontend exchanges a user's access token for a downscoped token before passing it to a third-party API.
- A gateway exchanges an inbound token for a service-mesh token whose
audmatches a downstream resource server. - A backend acts on behalf of a user it received an
id_tokenfor, asking ThunderID to mint an access token for that user against a specific resource server.
How It Works
How ThunderID Implements It
| Aspect | Behavior |
|---|---|
| Endpoint | POST /oauth2/token |
| Grant type | urn:ietf:params:oauth:grant-type:token-exchange |
subject_token_type (input) | urn:ietf:params:oauth:token-type:access_token · :refresh_token · :id_token · :jwt |
actor_token_type (input) | Same set as subject_token_type; used for delegation chains |
requested_token_type (output) | urn:ietf:params:oauth:token-type:access_token · :jwt only. id_token and refresh_token outputs are not supported. |
audience parameter | Accepted for RFC 8693 compatibility but does not determine the issued access token's aud claim |
resource parameter | RFC 8707 resource indicator. The issued access token is bound to exactly this resource server. Permission-bearing requests use defaultResourceServer when it is omitted. OIDC-only or scopeless requests use the application's token.accessToken.defaultAudience, falling back to client_id |
scope parameter | Requested scopes; downscoping is allowed, widening is rejected with invalid_scope. Final scopes must also be permissions on the target resource server and authorized for the issuing app or agent. |
| Client authentication | Required, same methods as the token endpoint |
| Issued token shape | issued_token_type is echoed in the response so the caller knows what it received |
Request Fields
| Field | Required | Description |
|---|---|---|
grant_type | Yes | urn:ietf:params:oauth:grant-type:token-exchange |
subject_token | Yes | The token being exchanged |
subject_token_type | Yes | The type URI of subject_token |
requested_token_type | No | Defaults to :access_token |
actor_token | No | Token of the acting party in delegation |
actor_token_type | No | Required when actor_token is supplied |
audience | No | Logical audience value from RFC 8693. It is ignored for the access-token aud; use resource to select the token audience. |
resource | No | Absolute URI of the target resource server (see Resource Indicators). If omitted, permission-bearing requests use the configured default resource server, while OIDC-only or scopeless requests use the application's token.accessToken.defaultAudience, falling back to client_id. |
scope | No | Requested scopes must be a subset of the subject token's grant |
Try It in ThunderID
- Console
- Dynamic Client Registration
- Open Applications or Agents in the ThunderID Console and select the client that will perform the exchange.
- Open the Advanced Settings tab.
- Under Grant Types, add
urn:ietf:params:oauth:grant-type:token-exchange. - Save.
POST /oauth2/dcr/register
Content-Type: application/json
{
"client_name": "API Gateway",
"grant_types": ["urn:ietf:params:oauth:grant-type:token-exchange"],
"token_endpoint_auth_method": "private_key_jwt",
"jwks_uri": "https://gateway.example.com/.well-known/jwks.json"
}
Exchange a Token
curl -X POST https://thunderid.example.com/oauth2/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "subject_token=$ACCESS_TOKEN" \
-d "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
-d "resource=https://api.example.com/payments" \
-d "scope=payments:read"
Response:
{
"access_token": "eyJhbGciOi...",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "payments:read"
}
Related Guides
- Resource Indicators, target the new token at a specific resource server
- Client Credentials, for service tokens that don't require a subject token
- Refresh Token, for renewing access tokens without exchange semantics