Why OAuth is not enough for agents: the flows we actually ship authenticate a person in a browser, or a confidential client with a secret. An unattended agent needs a workload principal you can revoke per session without disabling the human. OAuth can carry that principal, but it does not create it by default.
An agent on a WorkerPool is not in a browser. If you give it the user’s refresh token, the snapshot is now the user. If you give it a client secret, every actor that restores that image has the client. If you run a device-code flow and leave it unattended, you have built a token printer.
I still use OAuth pieces. I do not use OAuth as the agent’s name.
Why is OAuth not enough for agent identity?
Typical OAuth puts a user in front of a client and issues tokens that represent the user. An unattended agent is not that client.
If the token is the user’s, revoke means disabling the user. After the gym-class booking, the audit log says alice. You disable alice. The agent is still in the pool, and alice cannot log in. That is the wrong blast radius.
If the token is a long-lived client credential, it is a secret in a snapshot. Agent Substrate checkpoints memory and filesystem to object storage. A checkpoint is a copy. Copies leak. Agent checkpoint to object storage . Secretless AI agents exists because a better secret store still leaves the plaintext in the process.
The LiteLLM compromise harvested environment variables, .env files, cloud credentials, Kubernetes configs, and SSH keys. That list is where OAuth client secrets live in a normal agent deployment. Moving the secret to Vault changes where it rests, not where it is exposed.
Proof of agent identity
is the evidence requirement: which agent, which session, on whose behalf. sub=alice answers a different question.
What does OAuth still get right for agents?
Delegation. A human can grant a narrower access to a client. Token exchange can swap one audience for another. RFC 8707 resource indicators bind the issued token to one backend. RFC 8693 is the exchange. Those are the right primitives.
MCP’s Enterprise-Managed Authorization authenticates the employee through an IdP. The client logs the user in, exchanges the ID token for an ID-JAG, then presents the ID-JAG to the MCP authorization server for an access token. The IdP evaluates group membership at the exchange step, so access is grantable and revocable from one admin console. I documented what has actually shipped in the MCP agent identity gap . The wire trace is in the Cross App Access write-up.
For a developer running an MCP client on a laptop, a browser redirect is exactly right. For an agent running unattended in a namespace at 3 AM, there is no browser and no employee to redirect. The extension was not built for that case and does not claim to be.
Use those flows for the human-to-agent grant. Then stop. The agent still needs its own subject in the token. OAuth without that subject is a user impersonation leak with extra steps.
kagent v0.10.0 can send RFC 8707 resource indicators on its STS exchange. Both variables default to empty. Empty means a token that is good wherever that STS is trusted. Set them. kagent audience-bound tokens .
What does OAuth not give you for an unattended agent?
A subject that is the agent. sub=alice is alice. When alice’s agent books a gym class, the audit log should not say alice unless you meant impersonation. I want agent=gym-bot, act=alice or I cannot do proof of agent identity.
Session lifetime. User tokens last for the SSO policy. Agent turns last seconds. Bind the agent credential to the turn. Restore is the moment to mint. Suspend is the moment to drop. Suspend and resume agents .
Unattended operation. Client credentials OAuth is a stored secret. The MCP draft client-credentials extension is explicit about its prerequisite: pre-registered credentials established out-of-band. Dynamic Client Registration is not used in that flow. Somebody creates a credential and puts it somewhere the agent can read. JWT client authentication is better than a shared secret. It is still a registered client with a key it has to keep.
Attestation of the process. OAuth as usually deployed does not prove which workload is calling. A stolen refresh token works from a laptop. A stolen client secret works from a different pod. SPIFFE SVIDs answer which process. OAuth still has to carry a subject that is the agent, not only the user.
DPoP (SEP-1932) would bind an access token to a key pair the client controls, so a leaked token cannot be replayed by whoever picked it up. Workload Identity Federation (SEP-1933) would let a Kubernetes or SPIFFE JWT stand in for a registered client. Both were opened in December 2025. Both are still open pull requests. Until they land, the proof lives a layer down.
Why is X.509 not a substitute by itself?
Same split. A long-lived cert is a secret. A cert on a pod authenticates the pod. On a WorkerPool that is the worker, shared by every actor that lands in the bay. Nine agents on two workers, later 18 on the same two: one cert, many agents. kagent Agent Substrate .
A SPIFFE SVID is a short-lived cert minted from attestation, which is why SPIFFE for AI agents is on my list. The workload fetches the SVID from the local Workload API rather than from a file. The certificate lives minutes. Renewal is a background stream. There is nothing to copy into the ActorTemplate because nothing was written down. The gateway fails closed at startup if SPIFFE is enabled and no identity can be obtained.
X.509-SVIDs on the handshake are the form SPIFFE recommends when you can use them, because a JWT token can be replayed if it is captured in transit. That is still X.509. It is not a cert file in the image.
If someone says “we cannot use existing OAuth or X.509,” they are usually right about the existing deployments, not about the primitives. Rebuild the issuance. Do not paste a bearer token into the ActorTemplate.
How should you use OAuth and X.509 as plumbing instead of as the agent’s name?
Rebuild issuance around the agent, then use OAuth for the grant and the audience.
- Attest the workload. SPIFFE SVID from the Workload API, or a projected SA token with a named audience. Agent identity vs service account . The SA is the bootstrap. The minted token is the agent.
- Exchange for a scoped credential. RFC 8693 token exchange. RFC 8707 audience. Destination checks
auditself. Replay against a second backend is 403. Workload identity federation for agents . - Keep the human grant separate. ID-JAG, constrained delegation, lifetime in minutes, logged as “agent A for user U.” Do not copy alice’s cookie into the snapshot.
- Drop on suspend. The actor starts empty. One round trip at restore is the cost. A refresh token in object storage is the other cost.
Who gave the agent a badge still applies after the token is right: least privilege, a written approval list, an audit trail the agent cannot edit. Identity is only the first control. Zero trust architecture for AI agents still needs sandbox and egress. A valid OAuth token through an open network is a valid OAuth token through an open network.
Checklist
- No user refresh token in the actor, the image, or the snapshot.
- No client secret in the ActorTemplate.
- No long-lived cert file in the agent image.
- Agent subject is in the token.
sub=alicealone is a fail. - Audience bound to one backend. Replay elsewhere is 403.
- Revoke the agent without disabling the human and without deleting the worker SA.
- MCP EMA used for the employee grant, not as the WorkerPool name.
- Mint at restore, drop at suspend.
Field guide: Agent Substrate . The identity series: agent identity on Kubernetes .