Skip to Content
MuoCeon Docs
Third-party OAuth IntegrationAuthorization flows

Token exchange

RFC 8693 — a first-party admin console swaps a session token for a short-lived token of another audience.

Third-party applications do not use this flow

Token exchange is available only to first_party clients, used by MuoCeon's own admin console to carry an already signed-in user's session into the oauth / account resource endpoints. Third parties obtain tokens only through the authorization code flow.

What it solves

A first-party admin console holds an auth audience session token in the browser but needs to call endpoints under oauth (authorized application management) or account (account profile). Calling them with an auth token is blocked by audience validation — audience is a hard boundary, not a suggestion. Token exchange is the narrow door cut through that boundary: mint a short-lived, narrowly-scoped token for one specific audience.

Request

curl -X POST 'https://id.example.com/v1/oauth/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -u "$FIRST_PARTY_CLIENT_ID:$FIRST_PARTY_CLIENT_SECRET" \
  -d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
  -d 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
  -d "subject_token=$AUTH_ACCESS_TOKEN" \
  -d 'audience=oauth' \
  -d 'scope=oauth:grants:read'
ParameterRequiredNotes
grant_type✅urn:ietf:params:oauth:grant-type:token-exchange
subject_token✅A valid auth audience access token
subject_token_type✅Only urn:ietf:params:oauth:token-type:access_token
audience✅Only oauth or account
scopeOptionalOnly self-management scopes of the target audience

Explicitly rejected usages:

  • actor_token / delegated exchange — unsupported; passing it yields a parameter error.
  • requested_token_type other than an access token (e.g. requesting an id_token) — unsupported.
  • subject_token_type of refresh_token, SAML assertion, etc. — unsupported.
  • audience=auth — exchanging back to the session audience is not allowed.

Response

{
  "token_type": "Bearer",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "expires_in": 300,
  "access_token": "eyJ...",
  "scope": "oauth:grants:read"
}

How exchanged tokens differ

PropertyExchanged token
LifetimeFixed at 5 minutes
StorageNot persisted; no server-side record
RevocationCannot be revoked individually — wait for expiry
audTarget audience only (oauth or account), never auth
RefreshNo refresh token issued
Scope of useThe handful of requests needed for one page load

Because nothing is persisted, it inherently cannot be revoked or introspected. The 5-minute lifetime is therefore the only security boundary: exchange, use immediately, discard — never cache it in local storage for reuse across pages.

Choosing an audience

  • Reading authorized applications or revoking grants: audience=oauth with oauth:grants:read / oauth:grants:revoke.
  • Reading or updating the account profile: audience=account with account:profile:read / account:profile:write.
  • Needing both audiences: exchange twice; do not expect one token to cover two audiences.

The full scope list is in Scopes and audiences.

On this page