Skip to content

RFC 8693 Token Exchange

The token endpoint (packages/api/src/routes/v1/token.ts) supports grant_type=urn:ietf:params:oauth:grant-type:token-exchange, letting one token be traded for another — typically to federate an upstream token or to have a service act on behalf of a user.

Request

POST /:environmentId/as/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=...
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&actor_token=...              (optional — delegation rather than impersonation)
&actor_token_type=...
&audience=...                 (or resource=...)
&requested_token_type=urn:ietf:params:oauth:token-type:access_token
&scope=...

Only ...:access_token is issued as the requested_token_type by this emulator. audience (a logical service name) and resource (a URI) both land in the issued token's aud claim.

may_act / delegation

When actor_token is present, the issued token's delegation check (may_act) reads actor_token.client_id — the JWT is signed with both client_id (the RFC 8693 / OIDC-standard spelling) and the legacy clientId claim specifically so this lookup matches real PingOne's own token shape.

PingFederate-backed environments

If the environment's authProvider is pingfederate rather than emulated, a real client_credentials token used as a subject_token for a later token-exchange call must actually come from PingFederate itself — its own Access Token Manager is what validates a subject token during an exchange, so a self-signed emulator token wouldn't be recognized. The client_credentials grant on this endpoint already proxies through to PingFederate for such environments to keep this consistent — every other grant type here already did.