Token-exchange playground
RFC 8693 OAuth 2.0 Token Exchange in motion. The user has authenticated with a passkey. The agent has its own workload identity. The exchange produces a downscoped, time-bounded token whose act claim captures the delegation — so receiving services log both the user and the acting agent in their audit trail.
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=<user-jwt for u_marwan>
&subject_token_type=urn:ietf:params:oauth:token-type:jwt
&actor_token=<workload-jwt for spiffe://prod/agent/code-reviewer/v3>
&actor_token_type=urn:ietf:params:oauth:token-type:jwt
&audience=api.github.com
&scope=read%3Arepo%20write%3AissuesDelegated token claims (decoded)
{
"iss": "https://idp.lab.marwandiallo.com",
"sub": "u_marwan",
"azp": "a_code_reviewer",
"aud": "api.github.com",
"scope": "read:repo write:issues",
"iat": 1778451181,
"exp": 1778451781,
"jti": "22f5f76a-bfe0-4dd4-a05a-a030014ca170",
"act": {
"sub": "spiffe://prod/agent/code-reviewer/v3",
"azp": "a_code_reviewer",
"iss": "https://idp.lab.marwandiallo.com",
"attestation": "github-oidc"
},
"cnf": {
"jkt": "demo-thumbprint-code_r"
}
}What to look at: sub is the user; act.sub is the agent's workload identity; act.attestation records which platform vouched for the agent; aud and scope bound where and how the token can be presented; cnf.jkt sender-constrains the token (RFC 9449 DPoP).
What the audit log shows
2026-05-10T22:13:01.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=22f5f76a-bfe0-4dd4-a05a-a030014ca170Without act, the same line would say principal=u_marwan with no record of the agent — making the call indistinguishable from the user typing it in themselves.