Token-exchange playground
RFC 8693 OAuth 2.0 Token Exchange end-to-end. The IdP receives a user's passkey-bound subject_token and the agent's workload-attested actor_token, then mints a downscoped delegated token whose act claim records the agent identity. Edit any input — the three decoded JWTs and the claims diff re-run on every keystroke.
Tokens, side-by-side
Click any token to copy its compact JWS string. The signature segment is a deterministic demo hash (so the lab works offline); the JWT lab at /identity/jwt covers real signing.
Claims diff
Every claim in the exchanged token, coloured by origin. The point of token exchange is downscoping plus attribution — both should be visible here.
| claim | origin | value | why |
|---|---|---|---|
| iss | minted by STS | https://idp.lab.marwandiallo.com | Re-issued by the STS. Receiving services trust the STS, not the workload directly. |
| sub | from subject_token | u_marwan | Inherited from subject_token. RFC 8693 §1.2 requires the principal to remain the user. |
| azp | minted by STS | a_code_reviewer | Authorized party — the agent's IdP client_id. Resources can pin azp to a specific agent. |
| aud | minted by STS | api.github.com | Minted for this exchange. The actor_token's own aud was the STS itself; the resulting token is bound to the requested resource. |
| scope | narrowed by STS | read:repo write:issues | Subset of actor_token.scope. Token exchange exists to downscope, not preserve full authority. |
| iat | minted by STS | 1780871298 | Fresh per-exchange. exp drives the agent token's TTL; jti enables replay defence. |
| exp | minted by STS | 1780871898 | Fresh per-exchange. exp drives the agent token's TTL; jti enables replay defence. |
| jti | minted by STS | 1d86d1a0-88bb-435c-9713-cfbfcb8a2e72 | Fresh per-exchange. exp drives the agent token's TTL; jti enables replay defence. |
| act | from actor_token | {"sub":"spiffe://prod/agent/code-reviewer/v3","azp":"a_code_reviewer","iss":"https://idp.lab.marwandiallo.com","attestation":"github-oidc"} | Synthesised by the STS from actor_token. The act claim makes user→agent delegation visible in audit. |
| cnf | minted by STS | {"jkt":"demo-thumbprint-code_r"} | Sender-constraint (RFC 9449 DPoP / mTLS). Receiving services reject the token if presented by a key the cnf doesn't bind. |
RFC 8693 request
POST /token HTTP/1.1
Host: idp.lab.marwandiallo.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&requested_token_type=urn:ietf:params:oauth:token-type:access_token
&subject_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImlkcC0yMDI2LTA1In0.eyJpc3MiOiJodHRwczovL2lkcC5sYWIubWFyd2FuZGlhbGxvLmNvbSIsInN1YiI6InVfbWFyd2FuIiwiZW1haWwiOiJtYXJ3YW5AZXhhbXBsZS5jb20iLCJhdWQiOiJhZ2VudHMuaWRwLmxhYi5tYXJ3YW5kaWFsbG8uY29tIiwiaWF0IjoxNzgwODcxMjk4LCJleHAiOjE3ODA4NzQ4OTgsImFtciI6WyJwYXNza2V5IiwiaHdrIl0sImFjciI6IkFBTDMiLCJqdGkiOiJ1c3ItdV9tYXJ3YW4tMTc4MDg3MTI5OCJ9.ZGVtby1kODg1OTk0ZA
&subject_token_type=urn:ietf:params:oauth:token-type:jwt
&actor_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IndsLWdpdGh1Yi1vaWRjIn0.eyJpc3MiOiJodHRwczovL2lkcC5sYWIubWFyd2FuZGlhbGxvLmNvbSIsInN1YiI6InNwaWZmZTovL3Byb2QvYWdlbnQvY29kZS1yZXZpZXdlci92MyIsImF6cCI6ImFfY29kZV9yZXZpZXdlciIsImF1ZCI6Imh0dHBzOi8vaWRwLmxhYi5tYXJ3YW5kaWFsbG8uY29tIiwiaWF0IjoxNzgwODcxMjk4LCJleHAiOjE3ODA4NzE4OTgsImF0dGVzdGF0aW9uIjoiZ2l0aHViLW9pZGMiLCJzY29wZSI6InJlYWQ6cmVwbyB3cml0ZTppc3N1ZXMiLCJqdGkiOiJ3bC1hX2NvZGVfcmV2aWV3ZXItMTc4MDg3MTI5OCJ9.ZGVtby0wZWM1YjcwYw
&actor_token_type=urn:ietf:params:oauth:token-type:jwt
&audience=api.github.com
&scope=read%3Arepo%20write%3AissuesAudit log line
2026-06-07T22:28:18.000Z principal=u_marwan acting_as=spiffe://prod/agent/code-reviewer/v3 attestation=github-oidc aud=api.github.com scope="read:repo write:issues" ttl=600s jti=1d86d1a0-88bb-435c-9713-cfbfcb8a2e72Without act, the same line would read principal=u_marwan with no record of the agent — making the call indistinguishable from the user typing it themselves.
References
- RFC 8693 — OAuth 2.0 Token Exchange (§1.2 principal preservation; §4.1
actclaim) - RFC 9449 — Demonstrating Proof of Possession (DPoP) — sender-constraint via
cnf.jkt - NIST SP 800-63-4 (draft) — Digital Identity Guidelines — non-person entity treatment
- SPIFFE — workload identity URIs (the
act.subshape used above) - GitHub Actions OIDC, Azure managed identity, AWS Nitro Enclaves, GCP Workload Identity Federation — the four mainstream actor_token attestation surfaces.