{"slug": "trusted-ai-agent-transactions-part-2-pingfederate-token-exchange", "title": "Trusted AI Agent Transactions, Part 2: PingFederate Token Exchange", "summary": "Tokenetes has implemented a Transaction Token Service (TTS) using PingFederate's RFC 8693 token exchange protocol to securely bind user, agent, and workload identities in AI agent transactions. The design uses a subject token for the user and a SPIRE-issued actor token for the agent workload, ensuring neither can substitute for the other. PingFederate validates both tokens and issues a short-lived Txn-Token that carries immutable transaction context.", "body_md": "[Part 1](//01-the-identity-problem.md) separated the user, logical agent, runtime workload, agent execution, and transaction identities. This part shows where they are joined safely.\n\nIn the Tokenetes architecture, a Transaction Token Service, or TTS, uses the RFC 8693 token exchange protocol to mint a Txn-Token. This implementation assigns that logical role to PingFederate.\n\nThe agent sends:\n\n```\nsubject_token      = the user's access token\nsubject_token_type = access token\nactor_token        = the agent workload's JWT-SVID\nactor_token_type   = JWT\nrequested_token_type = urn:ietf:params:oauth:token-type:txn_token\naudience           = the transaction trust domain\nscope              = the narrow purpose of this transaction\n```\n\nThe subject token answers who authorized the external invocation. The SPIRE actor token is an agent-specific extension that proves which attested workload is requesting the exchange. Neither token is accepted as a substitute for the other. 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.\n\nThe following payloads 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. Raw tokens are never written to application or audit logs.\n\nThe subject token is the user's OAuth access token:\n\n```\n{\n  \"iss\": \"https://pingfederate.example\",\n  \"sub\": \"user-123\",\n  \"aud\": \"wai-agent-token-exchange\",\n  \"scope\": \"openid profile mcp:invoke\",\n  \"client_id\": \"wai-browser\",\n  \"iat\": 1770000000,\n  \"exp\": 1770000300,\n  \"jti\": \"subject-token-id\"\n}\n```\n\nIt represents the authenticated user. It does not prove which agent workload is presenting it, and it is not forwarded to the MCP gateway or downstream services.\n\nThe actor token is a JWT-SVID issued by SPIRE to the running agent workload:\n\n```\n{\n  \"iss\": \"https://spire.example.org\",\n  \"sub\": \"spiffe://example.org/agent/demo\",\n  \"aud\": [\n    \"urn:pingfederate:wai:token-exchange\"\n  ],\n  \"iat\": 1770000000,\n  \"exp\": 1770000060\n}\n```\n\nIt proves the SPIFFE workload identity to PingFederate. It does not identify the user, and it does not let the workload choose a logical `AgentID`\n\n.\n\nThe agent submits both tokens to PingFederate using a form-encoded RFC 8693 request:\n\n```\nPOST /as/token.oauth2 HTTP/1.1\nHost: pingfederate.example\nContent-Type: application/x-www-form-urlencoded\n\ngrant_type=urn:ietf:params:oauth:grant-type:token-exchange\n&subject_token=REDACTED_USER_ACCESS_TOKEN\n&subject_token_type=urn:ietf:params:oauth:token-type:access_token\n&actor_token=REDACTED_SPIRE_JWT_SVID\n&actor_token_type=urn:ietf:params:oauth:token-type:jwt\n&requested_token_type=urn:ietf:params:oauth:token-type:txn_token\n&audience=example.org\n&scope=mcp:invoke\n&request_details=%7B%22target%22%3A%22mcp-gateway%22%2C%22tool%22%3A%22customer.read%22%7D\n```\n\nThe values are redacted here intentionally. Tokens belong in the protected request body over validated TLS, never in a URL, log message, audit event, or error response.\n\nThe PingFederate Token Exchange Processor Policy validates both sides of the delegation:\n\n`AgentID`\n\nThe caller never supplies an authoritative logical agent identity. A workload such as `spiffe://example.org/agent/demo`\n\nis mapped to a configured logical agent such as `urn:agent:demo`\n\ninside the trusted policy boundary.\n\nAfter validating the subject and actor independently, PingFederate should issue a short-lived signed JWT conforming to the Transaction Tokens profile. Its protected header identifies it as a Txn-Token:\n\n```\n{\n  \"typ\": \"txntoken+jwt\",\n  \"alg\": \"ES256\",\n  \"kid\": \"pingfederate-transaction-signing-key\"\n}\n```\n\nThe body carries the immutable transaction context:\n\n```\n{\n  \"iss\": \"https://pingfederate.example/transaction-token-service\",\n  \"sub\": \"user-123\",\n  \"aud\": \"example.org\",\n  \"scope\": \"mcp:invoke\",\n  \"txn\": \"019...\",\n  \"req_wl\": \"spiffe://example.org/agent/demo\",\n  \"tctx\": {\n    \"target\": \"mcp-gateway\",\n    \"tool\": \"customer.read\",\n    \"agent\": {\n      \"id\": \"urn:agent:demo\",\n      \"instance_id\": \"019...\"\n    },\n    \"workload\": {\n      \"id\": \"spiffe://example.org/agent/demo\"\n    }\n  },\n  \"rctx\": {\n    \"authn\": \"urn:ietf:rfc:6749\"\n  },\n  \"iat\": 0,\n  \"exp\": 0\n}\n```\n\nThe response uses the normal OAuth token response shape:\n\n```\n{\n  \"access_token\": \"REDACTED_TRANSACTION_JWT\",\n  \"issued_token_type\": \"urn:ietf:params:oauth:token-type:txn_token\",\n  \"token_type\": \"N_A\"\n}\n```\n\nThe Txn-Token is not sent in the OAuth `Authorization`\n\nheader between internal workloads. It uses its dedicated header:\n\n```\nTxn-Token: REDACTED_TRANSACTION_JWT\n```\n\nThe token is not replay resistant, so TLS, its very short lifetime, trust-domain audience validation, and immediate-caller SPIFFE mTLS are all required. The same Txn-Token remains unchanged through the MCP gateway, MCP server, and protected API. Each service validates it before using any decoded claim.\n\nArbitrary claims are not copied from either input token. The original user access token and actor JWT-SVID stop at the exchange boundary and are not propagated to MCP servers or downstream APIs.\n\nThe repository already implements the difficult security boundaries: RFC 8693 exchange, independent subject and actor validation, workload-to-agent binding, short lifetime, immutable propagation, strict issuer and audience checks, SPIFFE mTLS at each hop, target authorization, and correlated audit.\n\nIt currently represents the result with application-specific transaction claims and bearer-token transport. To claim Tokenetes-style Transaction Tokens conformance, it still needs to emit `typ: txntoken+jwt`\n\n, request and return the Transaction Token type URN, map the context to `txn`\n\n, `scope`\n\n, `tctx`\n\n, `rctx`\n\n, and `req_wl`\n\n, use the trust domain as `aud`\n\n, and propagate the JWT in the `Txn-Token`\n\nheader. Validation must reject the older application token shape once that migration is enabled.\n\nThe reference implementation manages PingFederate with the official Terraform provider. Terraform owns the token processors, token exchange policy, access token manager, mapping, scope, and OAuth client.\n\nPlugin field names are discovered from the running PingFederate version before configuration is generated. This avoids guessing descriptor fields or weakening validation when product versions differ.\n\nStartup and configuration are separate trust operations. The profile starts the pinned product and installs the tested custom plugin. Scope creation and Terraform apply remain explicit operations, so starting a container cannot silently rotate an OAuth client secret or mutate token policy.\n\nThe repository-owned profile overlay is mounted read-only. A local generator creates short-lived bootstrap TLS material, datastore credentials, and system keys with cryptographically secure randomness. Those values are written to an ignored local file and never committed.\n\nThe clean-volume test then:\n\nThis is important because a configuration that only works against a long-lived developer volume is not a reproducible security control.\n\nPrevious: [The identity problem behind AI agents](//01-the-identity-problem.md)\n\nNext: [Binding a logical agent to a real workload with SPIRE](//03-spire-workload-identity.md)\n\nReferences: [CNCF Tokenetes](https://www.cncf.io/projects/tokenetes/) and the [IETF Transaction Tokens draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-transaction-tokens/).\n\nGitHub Repository: [https://github.com/darkedges/pf-tts](https://github.com/darkedges/pf-tts)", "url": "https://wpnews.pro/news/trusted-ai-agent-transactions-part-2-pingfederate-token-exchange", "canonical_source": "https://dev.to/darkedges/trusted-ai-agent-transactions-part-2-pingfederate-token-exchange-35pn", "published_at": "2026-08-23 23:01:49+00:00", "updated_at": "2026-08-23 23:14:20.294829+00:00", "lang": "en", "topics": ["ai-agents", "ai-infrastructure", "ai-safety"], "entities": ["Tokenetes", "PingFederate", "SPIRE", "RFC 8693"], "alternates": {"html": "https://wpnews.pro/news/trusted-ai-agent-transactions-part-2-pingfederate-token-exchange", "markdown": "https://wpnews.pro/news/trusted-ai-agent-transactions-part-2-pingfederate-token-exchange.md", "text": "https://wpnews.pro/news/trusted-ai-agent-transactions-part-2-pingfederate-token-exchange.txt", "jsonld": "https://wpnews.pro/news/trusted-ai-agent-transactions-part-2-pingfederate-token-exchange.jsonld"}}