Managing identities for agents
My understanding on how to solve identities for agents at scale
This is a small blogpost about my understanding of the solution to providing agents with secure identities. The answer seems to be SPIFFE-based identity. At least according to Google and Uber.
What is SPIFFE?
Secure Production Identity Framework For Everyone is an open standard that provides cryptographically verifiable identities to workloads. Basically passports for software. So instead of having your services proving who they are with shared passwords, API keys, or just trusting whatever is inside the network perimeter, each workload gets a cryptographic identity it can prove to anyone.
Every workload in SPIFFE gets a SPIFFE ID:
spiffe://<TRUST DOMAIN>/<YOU DECIDE>
The part called trust domain is your org, cluster, or other trusted domain. The you decide part is some sort of path that identifies your workload. This ID goes into a short-lived document called an SVID (SPIFFE Verifiable Identity Document). Services show each other their SVIDs, check them, and then talk.
There are two SVID flavours. An X.509 certificate with the SPIFFE ID in the URI SAN field (used for mTLS), or a JWT with the ID in the sub claim.
Soooo SPIFFE is also just the standard, the reference implementation is called SPIRE. The spec says what the IDs and SVIDs look like, but how a workload actually gets one is up to the implementation, so the stuff below is how SPIRE does it.
Then there’s something called the Workload API. On every host runs an agent (will call spiffe agent to not mix with ai agent) that exposes a local Unix socket. Apps on the host then connect to this socket and ask “who am I?”. The spiffe agent figures out who’s calling in two steps:
- Node attestation: the agent proves its machine is legit to a central server (the SPIRE server), using things like AWS instance identity document, a Kubernetes service account token or a TPM.
- Workload attestation: when a process hits the socket, the agent first asks the kernel who is on the other end of the socket (process id, user id, group id). That way the process can’t just lie about who it is. Then it uses that to ask things like kubelet or Docker which pod or image it is. This is then matched against registration rules such as “pods in namespace X with service Y get ID Z”
What’s nice with the SVIDs is that they are short-lived and auto-rotated. That’s SPIRE’s default, the spec itself doesn’t force it, but it’s kind of the whole point.
How can agents use this?
Well, now that we know a little bit about SPIFFE, how can this be used? The answer is SPIFFE + token exchange. More specifically OAuth 2.0 Token Exchange (RFC 8693 if you’re into that). Why do we need token exchange? SPIFFE is more used per workload, for identifying a workload, not used per request. SPIRE mints tokens based on what the process is, and it has no idea which user the agent is currently serving.
So we use token exchange which has a built-in way to express delegation.
- The user logs in and grants the agent permission through a normal OAuth login, which gives the agent a user token.
- The agent calls your STS with two things:
subject_token: the user’s token (who the agent is acting for), andactor_token: its own JWT-SVID. The JWT-SVID has to be minted with the STS as audience, since JWT-SVIDs are always made for a specific audience. - The STS checks both. Is this agent allowed to act for this user with this scope? If yes, it issues a new short-lived token that looks something like this:
{
"iss": "https://sts.acme.com",
"sub": "alice@acme.com",
"act": { "sub": "spiffe://acme.com/agents/monitoring-agent" },
"aud": "https://api.monitoring.com/alerts",
"scope": "alerts:read",
"exp": 1790000000
}
Now the API sees both: the user is sub and the agent is act. Only the API in aud should accept this token though, so if the agent needs to call something else it does a new exchange for that API.
If the agent calls another agent, you exchange again and the act claims nest, giving you the full chain (user → agent A → agent B). One thing that’s a bit confusing is that the outermost act is the latest actor, so it looks like this:
"act": {
"sub": "spiffe://acme.com/agents/agent-b",
"act": { "sub": "spiffe://acme.com/agents/agent-a" }
}
Also, only the outermost act should be used to decide if the call is allowed. The nested ones are more for logging and auditing, so you can see how you got there.
How does the agent know how to fetch the token? Two out of many possible solutions:
- The API tells the agent where to go: the agent calls the API without a token and gets a
401back. TheWWW-Authenticateheader points to the API’s protected resource metadata (RFC 9728), and that metadata says which authorization server gives out tokens for that API. MCP’s authorization spec uses this, so it’s already a thing in agent land. Your authorization server needs to support token exchange for this to work with the flow above though, MCP normally just sends you to a regular login. - The agent doesn’t know at all: a sidecar or egress proxy (like Envoy with a small filter that does the exchange) catches the outgoing calls, does the token exchange and adds the token. So the agent doesn’t have to know anything about tokens and the security stuff lives in infra you control. I think this is the nicer option, because I don’t really want an LLM handling tokens directly.
What about an Agent Registry?
Remember step 3, where the STS checks “is this agent allowed to act for this user with this scope?”. The STS needs to get that answer from somewhere, and that’s usually an agent registry. It’s basically a list of all the agents in your org, kind of like a phonebook and a rulebook in one. For every agent it keeps things like:
- its SPIFFE ID
- who owns it (team or person)
- what it does and which tools or APIs it’s allowed to call
- which scopes it can ask for and which users or groups can delegate to it
So when the token exchange happens, the STS looks up the SPIFFE ID from the actor_token in the registry and checks the request against it. If the agent isn’t in the registry it doesn’t get a token, simple as that. It also gives you one place to see every agent you have running, and to turn one off if it starts acting weird. Some registries also let agents find each other, like “which agent can handle alerts?”.
Wrapping up
So SPIFFE/SPIRE tells you which agent it is, token exchange tells you who it’s acting for, and the registry tells you if that’s allowed. What I’m still not sure about is who should own the rules in the registry, the team building the agent or the security team.
Further reading
The above was just my writing, my understanding of things, and it was not so detailed so here are some further reading links.
SPIFFE and SPIRE
- spiffe.io: the official site, with concept docs, SPIRE quickstarts and tutorials. Start with “Concepts” and the Kubernetes quickstart.
- SPIFFE specs on GitHub: the actual standards, including the SPIFFE ID, X.509-SVID, JWT-SVID, Workload API and Trust Domain/Bundle specs. The JWT-SVID one is short and worth reading.
- SPIRE on GitHub: the reference implementation. Check the docs folder for server/agent config and the plugin list (attestors, CredentialComposer and so on).
- SPIRE OIDC Discovery Provider: what you use for AWS, GCP or Azure federation with JWT-SVIDs.
- Solving the Bottom Turtle: a free book by the SPIFFE community. It gives great background on the “why.”
Token exchange and related OAuth RFCs
- RFC 8693, Token Exchange: the core spec, including the
actclaim and delegation vs. impersonation. - RFC 7523, JWT client authentication: how a JWT is used as a client assertion.
- RFC 8705, OAuth mTLS: client authentication with certs, and cert-bound tokens.
- RFC 8414, Authorization Server Metadata: the
/.well-knowndiscovery endpoint. - RFC 9728, Protected Resource Metadata: how an API points the client to its authorization server.
SPIFFE meets OAuth (drafts)
- OAuth SPIFFE Client Authentication: an IETF OAuth working group draft, currently at version -02 from June 2026. It lets workloads authenticate to OAuth authorization servers using JWT-SVIDs, WIT-SVIDs or X.509-SVIDs, with no client secrets needed. This is exactly the “agent talks to the STS” piece. IETF
- OAuth Client Registration on First Use with SPIFFE: a related draft on reducing client registration overhead for SPIFFE workloads through a “register on first use” approach. IETF
- IETF WIMSE working group: Workload Identity in Multi-System Environments. This is where workload identity across services, including agent use cases, is being standardized.
Agent-specific
- MCP Authorization spec: a real-world example of RFC 9728 and RFC 8414 discovery used by AI agents.