AI agents act for people, choose their own actions at runtime, and read content that may be hostile. The identity standards most systems run on assumed none of that. This post sets out the problem, the three layers needed to handle it, and a reference architecture built on standards that exist today.
The series has seven parts:
- Fundamentals (this post): the problem, the three layers, and the core architecture
- Agent identity: workload identity, client registration, SPIFFE and WIMSE
- Delegation: token exchange, actor chains, agent-to-agent hops
- MCP authorization in practice: OAuth 2.1, protected resource metadata, audience binding
- Tool-call authorization: AuthZEN and COAZ-MCP, decisions that depend on tool arguments
- Human in the loop: consent, step-up, approvals after a deny
- Operating in production: decision logs that record user and agent, policy lifecycle, signal stream health, revoking an agent, and incident response
Why agents break the identity model
Identity standards were built on two assumptions: a person is present, and the request reflects that person’s intent. When you click “Send” in a mail client, the OAuth token, the click and the intent all line up. AI agents break both assumptions. The agent decides at runtime which tools to call and with which arguments, often with nobody watching.
Every agent request therefore involves at least three parties:
- The user (the principal): whose authority the work runs under
- The agent (the actor): the software that decided to make this call
- The tool or resource: the MCP server, API or dataset being called
Most deployments today collapse the first two into one. The agent holds the user’s token, so every call it makes looks exactly like the user made it. That is the classic confused-deputy problem: a component with legitimate authority is tricked, or simply reasons its way, into using that authority for something nobody asked for. Prompt injection makes this concrete. A poisoned document can turn “summarize my inbox” into “forward the finance thread to this address”, and the resulting API call is fully authorized.
The result: the agent holds the full scope of the user’s access, but uses it in ways the user never anticipated and the logs cannot tell apart. This series is about closing that gap with standards that exist today, and being honest about where they stop.
Three questions, three layers
“Agent auth” is usually discussed as one problem. It is three, and each has a different answer and a different set of standards.
| Layer | The question | Answered by | Standards involved |
|---|---|---|---|
| Agent authentication | What software is this, and can it prove it? | The agent’s own credential | OAuth client authentication, Client ID Metadata Documents (IETF draft), DPoP (RFC 9449), workload identity (SPIFFE, IETF WIMSE working group) |
| Delegation | Who authorized it to act, for whom, and with what scope? | The access token and its claims | OAuth 2.1 authorization code + PKCE (IETF draft), Resource Indicators (RFC 8707), Rich Authorization Requests (RFC 9396), Token Exchange (RFC 8693) with the act claim |
| Per-action authorization | Can this specific call, with these arguments, happen right now? | A policy decision at call time | AuthZEN Authorization API 1.0 (Final), COAZ-MCP binding (Draft 1) |
The first two layers decide whether a token is valid. The third decides whether a valid token should be used for this call. OAuth was designed for the first two. A scope like files.write tells you the agent may write files; it cannot tell you whether deleting 4,000 of them at 3 a.m. matches what the user wanted.
This split is the core idea of the series: each layer has its own owner, its own failure modes and its own maturity. Later parts of the series take them one at a time.
Agents are not service accounts
A service account is a non-human identity, but its intent is fixed in code: the nightly export job always exports, and you can read the code to know what it will do. An agent is non-human too, but it chooses its actions at runtime, on someone else’s behalf, from inputs that may be hostile.
| Human user | Service account | AI agent | |
|---|---|---|---|
| Identity | A person, authenticated by the IdP | A workload credential | A workload credential plus a delegating user |
| Where intent comes from | The person, at the moment of the request | Code, reviewed before deployment | A model’s reasoning over prompts, documents and tool output |
| Predictability | Mostly | Fully | Low: the same task can produce different calls |
| Session | Interactive, bounded | Long-running, no user | Long-running, often with no user present |
| Can be manipulated by content it reads | Phishing, rarely at scale | No | Yes: prompt injection |
| Accountable party | The person | The owning team | Unclear unless the token records both user and agent |
The last row is the one that matters most in an incident. If your logs cannot say “agent X did this on behalf of user Y”, you cannot tell an agent mistake from a stolen session, and you cannot revoke one without the other.
In practice, agents need both treatments at once: a workload identity of their own (like a service account) and a delegated, bounded grant from a user (like a person’s session).
Where OAuth scopes stop
Scopes are often enough. A read-only tool on low-sensitivity data needs a well-scoped, short-lived, audience-bound token and nothing more. Putting a policy engine in front of it adds latency and a new failure mode, with no real security gain.
The calculation changes in three situations:
- The answer depends on conditions at call time. Rich Authorization Requests (RFC 9396) let a token carry structured details fixed at consent, such as a payment’s amount, currency and creditor. What a token cannot do is evaluate what only exists when the call happens: today’s running total, the user’s current risk, a payee added five minutes ago. Once those matter, you need a decision at call time.
- Nobody is watching. Background agents and agent-to-agent chains have no user to notice a strange call. The token must carry who delegated what, to whom, and expire quickly.
- One record across tools. If an auditor asks “what did agents do on Alice’s behalf last week?”, a central decision log answers it. Logs scattered across ten MCP servers do not.
Work down the questions in order: the first “yes” tells you which layer to add. Most tools in a typical deployment stop at the first question, and that is fine.
Reference architecture
The reference architecture has four paths: one to get a token, one to carry delegation across hops, one to decide each call, and one to react when something changes.
Steps 1 and 2 are authentication and delegation, steps 3 to 5 are the per-action layer, and step 6 keeps delegation intact across the next hop. The risk-signal path runs the whole time, independently of any request.
The authentication path: getting a token
Under the current MCP authorization spec, a protected MCP server is an OAuth 2.1 resource server and the MCP client is an OAuth client. The client calls the server, gets a 401 pointing to the server’s Protected Resource Metadata (RFC 9728), and discovers the authorization server from there.
The client then needs a client ID. The spec now prefers Client ID Metadata Documents, where the client is identified by an HTTPS URL it controls, and marks Dynamic Client Registration as deprecated. The user consents through authorization code with PKCE, and the client validates the iss in the response (RFC 9207) to stop mix-up attacks.
The delegation path: one token, one audience
The client must send the RFC 8707 resource parameter in both the authorization and token requests, and the server must reject any token not issued for it. A server must not accept or pass along any other token.
That creates a problem for the next hop. When the MCP server needs to call a downstream API, it cannot reuse the token it received. It exchanges it through OAuth 2.0 Token Exchange (RFC 8693) for a new token aimed at the downstream API.
The new token carries an act claim naming the current actor. Nested act claims record earlier actors, so the chain stays visible: Alice, via the finance assistant, via this MCP server. RFC 8693 is explicit that prior actors are informational only and must not drive access control, so policy should key on the user and the current actor.
Two OAuth working-group drafts extend this. Identity Chaining, now in the RFC Editor queue, carries identity across trust domains by combining Token Exchange with JWT authorization grants (RFC 7523). Transaction Tokens carry identity and authorization context through internal services inside one trust domain. The Delegation part of the series covers the mechanics.
The per-action path: deciding each call
The gateway (or the MCP server itself) acts as a Policy Enforcement Point. Before forwarding a tools/call, it sends an AuthZEN evaluation request to a PDP with the user, the agent, the tool and the arguments. Only "decision": true lets the call through. The COAZ-MCP draft standardizes this mapping; until it is final, you write the mapping yourself.
When a scope is simply missing, the MCP spec has its own step-up: the server answers 403 with insufficient_scope, and the client re-authorizes with the larger scope set. That handles “you need more scope”. It does not handle “this specific transfer needs a human”, which is the PDP’s job.
The async path: reacting when something changes
Tokens are issued once; risk changes all the time. If the user’s session is revoked or their device falls out of compliance, a CAEP event delivered over the OpenID Shared Signals Framework (both Final) should reach the PDP and the authorization server. The next tool call is then denied, even though the agent’s token is still valid. Agents often hold tokens far longer than people do, so this path matters more for them than for interactive users.
What a per-call decision looks like
Here is an AuthZEN evaluation request an MCP gateway might send before letting an agent call a transfer_funds tool. It follows the shape of a COAZ-MCP declared mapping: the user from the token’s sub, the agent from its client_id, the action and resource taken from the tool call, and the arguments placed in context where policy can act on them. The gateway sends it as POST /access/v1/evaluation:
{
"subject": { "type": "identity", "id": "alice@example.com" },
"action": { "name": "transfer_funds" },
"resource": { "type": "account", "id": "acct-ops-001" },
"context": {
"agent": "https://agents.example.com/finance-assistant",
"amount": 25000,
"currency": "EUR",
"payee_known": false,
"user_present": false
}
}
A policy can now say something a token alone cannot evaluate: an agent may initiate payments up to 1,000 EUR to known payees; anything larger, or to a new payee, needs the user’s confirmation. The PDP returns "decision": false with context telling the PEP to ask the user. The Human in the loop part of the series covers that flow.
The request and response shape is Final in AuthZEN Authorization API 1.0: subject, action and resource are required, context is optional, and the response is a boolean decision with an optional context the PEP can use. The mapping from an MCP tool call is defined by COAZ-MCP, still Draft 1. Without a declared mapping, its default uses tools/call as the action and the tool name as the resource; the payment fields above assume the tool declares its own mapping.
Failure modes
| Failure mode | What goes wrong | Mitigation | Reference |
|---|---|---|---|
| Token passthrough | Every MCP server becomes a credential relay, and the downstream API can no longer tell who is calling | A new token per hop, issued for that hop’s audience | MCP Authorization 2026-07-28; RFC 8693 |
| Tokens without a bound audience | A compromised server can replay a token it received at any other server | Send resource on every authorization and token request; servers reject tokens not issued for them | RFC 8707 |
| Long-lived bearer tokens held by agents | A standing credential with no user present; if stolen, it works from anywhere | Short lifetimes, re-delegation for long tasks, sender-constrained tokens, revocation that reaches the agent | RFC 9449 (DPoP); CAEP 1.0 |
| Injected intent | A prompt-injected agent makes a call its token fully allows | A decision per call on high-risk tools, using the arguments; user confirmation where needed | AuthZEN 1.0; CIBA Core 1.0 |
| Fail-open by accident | The gateway cannot reach the PDP and lets tools/call through | Decide per tool: fail-closed for payments and deletion, fail-open with logging only for low-risk reads | No standard; a design decision |
Token hygiene fixes the first three rows. It cannot fix the fourth, which is why the per-action layer exists. The fifth is decided whether or not you write it down: if you haven’t, your gateway’s default has decided for you.
Standards status (November 2026)
The authentication and delegation layers rest on finished RFCs. The per-action layer has a final core API and draft agent profiles. The agent-identity layer is the least settled: one IETF working-group draft addresses AI agents directly, next to many individual drafts.
| Standard | Status (checked 14 Nov 2026) | Layer | What it gives you |
|---|---|---|---|
| MCP Authorization, revision 2026-07-28 | Current revision; authorization itself is OPTIONAL | Authentication + delegation | MCP server as OAuth resource server, PRM discovery, mandatory resource parameter, audience validation, no token passthrough |
| OAuth 2.1 | IETF WG draft -17 (Oct 2026); MCP still cites -13 | Delegation | PKCE everywhere, no implicit grant |
| RFC 9728 Protected Resource Metadata | RFC | Authentication | How a client discovers which authorization server protects a resource |
| RFC 8707 Resource Indicators | RFC | Delegation | Tokens bound to one audience |
| RFC 8693 Token Exchange | RFC (Jan 2020) | Delegation | A narrower token for the next hop, with an act claim naming the actor |
| RFC 9396 Rich Authorization Requests | RFC (May 2023) | Delegation | Structured grants (actions, amounts, creditors) fixed at consent |
| RFC 9449 DPoP | RFC (Sep 2023) | Agent authentication | Tokens bound to the client’s key pair |
| OAuth Client ID Metadata Document | IETF WG draft -03 (Nov 2026); MCP says SHOULD support and deprecates Dynamic Client Registration | Agent authentication | A client identified by an HTTPS URL it controls |
| OAuth Identity Chaining | IETF WG draft -17, in the RFC Editor queue | Delegation | Carrying identity across trust domains |
| OAuth Transaction Tokens | IETF WG draft -11, WG consensus | Delegation | Identity and authorization context through internal services |
| IETF WIMSE working group | Active WG; its working-group draft draft-ietf-wimse-aims (-01, Nov 2026) applies WIMSE and OAuth to AI agents | Agent authentication | Workload identity for HTTP/REST call chains |
| AuthZEN Authorization API 1.0 | Final (11 Jan 2026) | Per-action | A standard PEP-to-PDP decision call |
| COAZ-MCP Binding | OpenID WG draft, Draft 1 (13 Feb 2026) | Per-action | A standard mapping from MCP tools/call to an AuthZEN request |
| AuthZEN Access Request and Approval Profile | OpenID WG draft | Human in the loop | What a PEP does with a “denied but requestable” decision |
| CIBA Core 1.0 | Final (2021) | Human in the loop | User approval on a separate device, with no browser redirect |
| Shared Signals Framework 1.0 and CAEP 1.0 | Final | Async | Session and risk events pushed between systems |
| Individual IETF agent drafts | Many, several already expired (for example On-Behalf-Of User Authorization for AI Agents) | Agent identity + delegation | Ideas, not standards: watch, don’t build on them yet |
Final is not the same as deployed. AuthZEN 1.0 is Final, but its agent bindings are drafts, and OAuth 2.1 itself is still a working-group draft. When a vendor says “standards-based agent authorization”, ask which documents and which versions from this table.
Open question to verify before publishing: the GA status and scope of vendor agent-identity products (for example Okta for AI Agents and Microsoft Entra Agent ID), and whether either uses these standards.
What’s next
The three layers are not equally mature, so start where the standards are solid. Get audience-bound, short-lived, delegated tokens right first. Then add per-call decisions for the handful of tools that can move money, delete data or reach outside your tenant.
Part 2 starts at the bottom of the stack: how an agent proves what it is before anyone asks whom it acts for. That covers workload identity, client ID metadata documents, SPIFFE and WIMSE, and why “the agent uses the user’s token” is not an identity.
Sources
- Model Context Protocol, Authorization, revision 2026-07-28
- The OAuth 2.1 Authorization Framework, draft-ietf-oauth-v2-1
- RFC 8693: OAuth 2.0 Token Exchange
- RFC 8707: Resource Indicators for OAuth 2.0
- RFC 9207: OAuth 2.0 Authorization Server Issuer Identification
- RFC 9396: OAuth 2.0 Rich Authorization Requests
- RFC 9449: OAuth 2.0 Demonstrating Proof of Possession (DPoP)
- RFC 9728: OAuth 2.0 Protected Resource Metadata
- OAuth Client ID Metadata Document, draft-ietf-oauth-client-id-metadata-document
- OAuth Identity and Authorization Chaining Across Domains, draft-ietf-oauth-identity-chaining
- Transaction Tokens, draft-ietf-oauth-transaction-tokens
- IETF WIMSE working group
- OpenID AuthZEN Authorization API 1.0 (Final)
- OpenID AuthZEN working group specifications
- COAZ-MCP: COAZ Binding for the Model Context Protocol, Draft 1
- OpenID Shared Signals working group specifications
- OpenID Client-Initiated Backchannel Authentication (CIBA) Core 1.0
- On-Behalf-Of User Authorization for AI Agents (expired individual draft)