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.