← marwandiallo.comlabs

Agent identity

Every conversation about passkeys ends with the same question: great, but my AI agent has an OpenAI API key in a config file — how is that any better than a password? It's not. The fix uses the same building blocks as WebAuthn, applied to workloads.

Three identities, not one

When an agent runs a task on your behalf, three identities are in play. Most platforms collapse them and that's where the security model breaks.

Workload identity primitives

OIDC for workloads (federated tokens)
GitHub Actions, AWS, Azure, GCP, Kubernetes — they all issue OIDC tokens to running workloads. The token is short-lived, attested by the platform, and the relying party verifies it the same way it verifies a human's ID token. No long-lived API key needed.
Real example: the GitHub Actions runner on this lab's CI could authenticate to Vercel with no stored secret — just an OIDC token Vercel verifies against GitHub's JWKS.
SPIFFE / SPIRE
The CNCF standard. Every workload gets an X.509 SVID — a short-lived cert with a SPIFFE ID like spiffe://prod/agent/code-reviewer/v3. The SPIRE agent mints them on demand based on attested workload selectors (process UID, k8s service account, hardware TPM). Used by Bloomberg, Spotify, and most large platforms running mTLS at scale.
Hardware attestation
The agent doesn't say it's running on a trusted node, it proves it via TPM quote, AWS Nitro attestation, Azure Confidential Compute, or Apple App Attest. The same property that makes a passkey unphishable (non-exportable, hardware-bound) extends to the workload credential.
Token-bound delegation
OAuth 2.0 token exchange (RFC 8693) lets a user's passkey-issued token be downgraded into a narrower, time-boxed token for the agent. The agent's token is bound to its workload identity via DPoP (RFC 9449), so a stolen token can't be used elsewhere.

The unsolved problem: agent-on-behalf-of-user

Today, most agent platforms ship one of these two broken models:

  1. Shared secret model. The agent has a long-lived API key for the LLM provider, plus another long-lived token for every tool it calls (GitHub PAT, calendar token, Stripe key). Steal the agent's container, get keys to everything. This is most agents in production right now.
  2. Impersonation model. The agent uses your credentials directly. Whatever you can do, the agent can do, with no audit distinction. Your access logs say "user did X" when the agent did X.

What a clean model looks like

  1. User authenticates with a passkey → receives a session token at AAL2 / AAL3.
  2. User invokes agent → token-exchange (RFC 8693) downgrades the session into a narrower agent token: scope = read:repo write:issues, audience = github.com, ttl = 5 minutes, actor claim = spiffe://prod/agent/code-reviewer/v3.
  3. Agent calls GitHub. GitHub sees both sub (the user) and act (the agent), logs the agent identity in the audit trail, enforces scope.
  4. Token expires in 5 minutes. Agent that needs to keep working re-requests, which the user can revoke at any time without changing their own credentials.

This is what OIDC's act claim and OAuth 2.0 Token Exchange were designed for. Almost nobody uses them yet for agents. That's the gap.

Practical guidance for builders

Read next

Phishing-resistant MFA, on the wire →
Try a passkey registration →
Inspect a token (try one with an act claim) →