Short answer: give the agent its own identity, then issue it a short-lived token that names both the user and the agent and carries the narrowest scope that finishes the job. If your agent authenticates as the user, you have not built delegation — you have built impersonation, and every prompt injection inherits the user's full authority.
The mistake that makes everything else unfixable
The default shortcut is to hand the agent a credential that already exists: the user's session token, or one unscoped API key shared by the whole deployment. Three named anti-patterns, and they fail the same way:
- inherited user tokens — the agent is indistinguishable from the human in every downstream audit log
- unscoped API keys — a tool that only needs to read calendars can drain your billing API
- standing credentials — nothing expires, so a leak is permanent
An agent is neither a human nor a service account. It has a delegation chain — this agent, acting for this user, with this scope — and neither existing identity class can express that.
What a delegated token actually looks like
Four claims carry the whole model:
| claim | meaning | |---|---| | sub | the human the action is performed for | | act | the agent performing it | | aud | the single resource server the token is valid against | | scope | the narrowest verb that completes the task |
Scope is where most of the safety lives. email.draft and email.send are one word apart and a world apart in blast radius — an agent that can draft is reviewable, an agent that can send is not.
Keep the two protocols separate: OIDC authenticates the human, OAuth 2.1 authorizes the tool call. Conflating them is precisely what lets a compromised agent reuse a session cookie against a system nobody authorized.
MCP makes this concrete
MCP servers are OAuth 2.0 resource servers and must implement RFC 9728 (Protected Resource Metadata). The discovery flow:
- Unauthenticated call returns 401 with
WWW-Authenticate: Bearer resource_metadata="https://…/.well-known/oauth-protected-resource" - Client fetches that PRM document and reads
authorization_servers - Client fetches the authorization server's
/.well-known/oauth-authorization-serverfor endpoints - Authorization-code flow with PKCE; the token request carries
resource(RFC 8707) so the issued token's audience binds to that one server
Two details implementations get wrong:
- Return 401 at the HTTP boundary, not a tool-level error. A handler that replies
{"error":"unauthorized"}with HTTP 200 is invisible to the client's auth machinery — the client never learns where to get a token, so the flow silently never starts. - Bind the audience. Without the
resourceparameter, a token minted for one MCP server is replayable against every other server that trusts the same issuer.
Multi-agent: narrow on the way down
A subagent must receive a token derived from the parent's authority with fewer scopes — never a copy of the parent's credentials. Skip this and your agent hierarchy becomes a privilege-escalation ladder: the orchestrator holds broad authority, and any injected subagent borrows all of it.
Internal traffic is a different problem
Public and browser-based agents use OAuth 2.1 with PKCE for secret-less proof-of-possession. Service-to-service traffic inside your own perimeter is better served by SPIFFE/SPIRE X.509 SVIDs over mTLS. CNCF's 2026 shorthand is worth memorizing: SPIFFE for identity, OAuth 2.0 for access delegation, OPA for policy.
The honest state of the standards
No dominant agent-identity standard exists yet. The OpenID Foundation has published a consensus whitepaper on agentic identity; OIDC-A 1.0 is a proposal extending OIDC with delegation-chain validation and attestation; the IETF has an AIP track. All still moving.
So build on the parts that are stable — OAuth 2.1, RFC 9728, RFC 8707, PKCE — and keep agent-specific identity claims behind an abstraction you can swap. The failure modes above are already well understood; the standard that names them is not settled.