BuildNot yet confirmed elsewhere1 publisher3 min readPublished
Two tokens, one exchange: binding a user's consent to an attested agent workload
A PingFederate write-up wires RFC 8693 token exchange to a SPIRE JWT-SVID so neither the user's token nor the workload's proof can stand in for the other. The lifetimes tell you where the risk sits.
The Engineer · Build desk

What happened
- A Tokenetes-style design puts PingFederate in the Transaction Token Service role, minting Txn-Tokens through RFC 8693 token exchange.
- The subject token says who authorized the call and the actor token says which attested workload is asking; neither is accepted in place of the other.
- The agent cannot name its own logical identity; trusted policy configuration maps the verified SPIFFE ID to a configured AgentID.
Why it matters
- constraint A stolen user access token buys nothing on its own here. The thief also has to be executing inside a workload SPIRE will vouch for at the moment of the exchange.
- decision A sixty-second SVID forces a choice about runtime topology: the SPIRE agent becomes a live dependency of every tool call, with its availability now part of your transaction success rate.
- exposure The SPIFFE-ID-to-AgentID mapping becomes the thing worth attacking, because editing one row grants a workload the authority of a different logical agent.
- capability Gateways and downstream services can now authorize a call without ever holding the user's credential, which removes them from the blast radius of a leaked access token.
Two clocks mark out the trust model. In the illustrative payloads, the user's access token runs from `iat` 1770000000 to `exp` 1770000300 [12], five minutes of validity [13]. The workload's JWT-SVID from SPIRE runs to 1770000060 [15], sixty seconds [14], one fifth as long [16]. The human's authorization is allowed to be stale; the proof that an attested process is making the call is not. In practice that means the agent has to reach its SPIRE agent close to the moment it posts to the token endpoint, which puts workload attestation on the request path rather than in startup configuration.
The refusal is the interesting part. The caller never supplies an authoritative logical agent identity: `spiffe://example.org/agent/demo` is mapped to a configured logical agent such as `urn:agent:demo` inside the trusted policy boundary [18], and the actor token itself does not let the workload choose an AgentID [17]. So the question "which agent is this" is answered by a table in PingFederate, not by anything the agent says about itself. Whoever can edit that mapping can promote a workload into a different logical agent without touching a line of agent code.
Validation is described as two independent processors, one for the user token and one for the SVID, checking signature, issuer, audience, time, key ID and algorithm before the SPIFFE ID is mapped and a short-lived transaction JWT is issued [6]. The write-up is explicit that claims are only consumed after the whole token passes those checks, and that raw tokens are never written to application or audit logs [2], with tokens confined to the request body over validated TLS rather than URLs, log lines, audit events or error responses [5]. That last constraint is the one operators break first, because a redacted token is much harder to debug than a logged one.
Downstream, the user's access token stops at the exchange. It is not forwarded to the MCP gateway or to any downstream service [3]. What travels instead is a Txn-Token signed with ES256 and typed `txntoken+jwt` [19], carrying the requesting workload in `req_wl` and a transaction context naming the target, the tool `customer.read`, the logical agent and an instance ID [20]. The gateway's authorization decision now keys on fields that used to be telemetry. The response comes back in the ordinary OAuth token response shape [21], so nothing in the client transport has to change.
Read the source for what it is: a design, not a deployment record. The payloads are labelled illustrative decoded claims [2], the Txn-Token body carries `iat` 0 and `exp` 0 [20], and the text says PingFederate *should* issue the token after validating subject and actor separately [7]. The architectural point stands on its own, since the base Transaction Tokens profile leans on authenticated TTS clients and this design adds explicit actor evidence on top [11]. But nobody has shown you a policy that runs.
What to watch
- Whether a later part publishes a running policy issuing tokens with real iat and exp values instead of illustrative payloads.
- Whether the MCP gateway side documents how it verifies req_wl and tctx, since a transaction token nobody checks is decoration.
- How the SPIFFE-ID-to-AgentID mapping is change-controlled: who can add an entry, and whether that change is itself audited.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence44
- Adoption10
- Hype gap−5
- Incentives42
- Confidence55
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
The agent sends subject_token = the user's access token, actor_token = the agent workload's JWT-SVID, requested_token_type = urn:ietf:params:oauth:token-type:txn_token, audience = the transaction trust domain, and scope = the narrow purpose of the transaction.
- [2]
The payloads shown are illustrative decoded claims; in the implementation claims are used only after the complete token has passed signature, issuer, audience, algorithm, key ID and time validation, and raw tokens are never written to application or audit logs.
- [3]
The subject token represents the authenticated user, does not prove which agent workload is presenting it, and is not forwarded to the MCP gateway or downstream services.
- [4]
The agent submits both tokens in a form-encoded POST to /as/token.oauth2 with grant_type urn:ietf:params:oauth:grant-type:token-exchange, audience example.org, scope mcp:invoke and request_details naming target mcp-gateway and tool customer.read.
- [5]
Tokens belong in the protected request body over validated TLS, never in a URL, log message, audit event or error response.
- [6]
The PingFederate Token Exchange Processor Policy validates the subject token with a user token processor, validates the JWT-SVID signature, issuer, audience, time, key ID and algorithm with a SPIRE processor, maps the verified SPIFFE ID to a logical AgentID via trusted configuration, and issues a small short-lived transaction JWT for the requested audience.
- [7]
The write-up states that after validating the subject and actor independently, PingFederate should issue a short-lived signed JWT conforming to the Transaction Tokens profile.
- [8]
Part 1 of the series separated the user, logical agent, runtime workload, agent execution, and transaction identities; Part 2 shows where they are joined.
- [9]
In the Tokenetes architecture, a Transaction Token Service uses the RFC 8693 token exchange protocol to mint a Txn-Token; this implementation assigns that logical role to PingFederate.
- [10]
The subject token answers who authorized the external invocation and the SPIRE actor token proves which attested workload is requesting the exchange; neither token is accepted as a substitute for the other.
- [11]
The base Transaction Tokens profile relies on authenticated TTS clients; this design additionally carries explicit actor evidence so PingFederate can bind a logical AI agent to its runtime workload.
- [12]
The example subject token has iss https://pingfederate.example, sub user-123, aud wai-agent-token-exchange, scope "openid profile mcp:invoke", client_id wai-browser, iat 1770000000 and exp 1770000300.
- [13]
The example subject token has a validity window of 300 seconds, or five minutes.
- [14]
The example SPIRE JWT-SVID has a validity window of 60 seconds.
- [15]
The example actor token is a JWT-SVID issued by SPIRE with iss https://spire.example.org, sub spiffe://example.org/agent/demo, aud urn:pingfederate:wai:token-exchange, iat 1770000000 and exp 1770000060.
- [16]
The user access token in the example lives five times as long as the workload's JWT-SVID.
- [17]
The actor token proves the SPIFFE workload identity to PingFederate; it does not identify the user and does not let the workload choose a logical AgentID.
- [18]
The caller never supplies an authoritative logical agent identity; a workload such as spiffe://example.org/agent/demo is mapped to a configured logical agent such as urn:agent:demo inside the trusted policy boundary.
- [19]
The Txn-Token protected header is typ txntoken+jwt, alg ES256, kid pingfederate-transaction-signing-key.
- [20]
The Txn-Token body carries iss https://pingfederate.example/transaction-token-service, sub user-123, aud example.org, scope mcp:invoke, a txn id, req_wl spiffe://example.org/agent/demo, a tctx naming target mcp-gateway, tool customer.read, agent id urn:agent:demo with instance_id, workload id, rctx authn urn:ietf:rfc:6749, and iat 0 and exp 0.
- [21]
The token exchange response uses the normal OAuth token response shape.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toTrusted AI Agent Transactions, Part 2: PingFederate Token Exchange
1 article · August 23, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.
Topics
- AI Agent IdentityFollow
- OAuth Token Exchange and DelegationFollow
- Workload Identity and AttestationFollow
- Enterprise IAM IntegrationFollow
- Transaction TokensFollow
- Model Context Protocol SecurityFollow
Entities
- PingFederateFollow
- SPIREFollow
- SPIFFEFollow
- RFC 8693 OAuth 2.0 Token ExchangeFollow
- Transaction Tokens profileFollow
- TokenetesFollow
- Model Context ProtocolFollow