Token exchange
Promote a user's token from a public client to one minted for your confidential backend (RFC 8693).
RFC 8693 token exchange, used to promote a user’s token: a backend holding
a confidential client trades a token minted for a public client (the
browser’s) for one minted to itself. Same user, tenant and session; aud
becomes the confidential client and azp names the public client it came
from.
Authorization
The confidential client’s secret. See
Client authentication. The user’s access token goes in
the body as subject_token.
Body
Form-encoded (application/x-www-form-urlencoded).
| Field | Type | Required | Description |
|---|---|---|---|
grant_type | string | Yes | urn:ietf:params:oauth:grant-type:token-exchange |
subject_token | string | Yes | A valid user access token from this org. |
subject_token_type | string | Yes | urn:ietf:params:oauth:token-type:access_token |
requested_token_type | string | No | If sent, urn:ietf:params:oauth:token-type:access_token. |
client_id | string | No | Required with client_secret_post; taken from the Authorization header with client_secret_basic. |
client_secret | string | No | Required with client_secret_post. |
Response
200, with Cache-Control: no-store. There is no refresh token, and the new
token never outlives the subject token: when it lapses, promote the subject
again.
{
"token_type": "Bearer",
"access_token": "eyJ…",
"expires_in": 3600,
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"expires_at": "2026-10-18T13:00:00.000Z"
}
Errors
Every refusal is 400 with an RFC 6749 code and nothing else.
| Status | error | When |
|---|---|---|
400 | invalid_request | subject_token_type or requested_token_type is not the access-token type, subject_token is missing, or client_id is malformed. |
400 | invalid_client | Unknown or public client, a wrong secret, or a disallowed IP. |
400 | invalid_grant | subject_token does not authenticate on this org, or is not a user token. |
400 | invalid_request | The host is not an org. |
400 | unsupported_grant_type | grant_type is not one of the five the endpoint accepts. |
Example
curl -X POST https://acme.loginwith.dev/oauth/token \
-u cli_backend…:secret \
-d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
-d subject_token=eyJ… \
-d subject_token_type=urn:ietf:params:oauth:token-type:access_token