OAuth·for·Agents

Delegated access in the agentic era

Agents need to log in to things.

Not with your API key. Not with a copy of your password. With an identity of their own, permission you actually granted, and a trail you can read afterwards.

It's OAuth, and almost all of it already exists. What's been missing is a picture of how the pieces fit together for software that decides at runtime what it will touch. Seven scenarios, each adding one piece.

First, who's in the room

Four parties, and one of them is new.

One of the parties is new, and it changes where the credentials must not go.

What an agent actually does
  1. User or System→Agent

    Optional. A person, a batch job, or another agent hands the agent a task. It may also simply run on a schedule with no instruction at all.

  2. Agent→Model

    The agent gives the model the context it has: the request, its system prompt, the available tools, and whatever earlier tool output it accumulated.

  3. Model→Agent

    The model returns output determining which tool, service or resource gets called next. This is where a task becomes a decision about what to touch.

  4. Agent→Tools · Services · Resources

    The agent calls one or more external endpoints, any of which may itself be another agent. This is the arrow the rest of this site is about.

  5. Tools · Services · Resources→Agent

    Results come back, become more context, and steps 2 to 5 run again until the agent decides it's finished.

  6. Agent→User or System

    Optional. It answers, or asks for more input, or reports intermediate progress.

The model is a protected resource too. Step 2 is an authenticated API call needing what any other does: an identity for the caller, a scoped token, an audit trail. There's no useful difference between an API, an MCP server, and another agent wearing a tool's clothes.

The model must never be able to reach a credential. Steps 2 and 3 loop through a component that reads untrusted input and can be talked into printing things, so a credential in that context window is a credential you have published. The one rule here with no exceptions, and the argument for credentials the agent never holds on disk.

The same six flows

Six flows. Four parties. All of it OAuth.

The four parties sit in the same place in every diagram, and the flows get longer as you move right. Solid arrows are requests, dashed are responses. The fourth party stays idle until the last tab: crossing a company boundary is the only flow that needs a second authorization server.

An API key replaced with a key the agent holds. The token it earns is bound to that key, so a copy is worth nothing.

The agent authenticating as itself The agent signs a client assertion with its own private key and asks the authorization server for a token scoped to the ticketing API. It gets back a DPoP-bound access token good for five minutes, then calls the API with that token and a fresh DPoP proof. Agent Resource Authorization Server Enterprise IdP POST /token client assertion (private_key_jwt) + DPoP proof + resource= DPoP-bound access token, good for five minutes GET /tickets Authorization: DPoP … plus a fresh proof over this method and URL 200 OK · the log names the agent's client_id

Full walkthrough: replace the API key →

Delegation without a browser. The token names the person and the agent, so the API's log has both.

The device authorization grant, DPoP-bound The agent asks for a device code, shows the user a short code and a URL, and polls while the person signs in on their phone and approves. It receives a DPoP-bound access token whose subject is the person and whose client is the agent, then reads the calendar. Agent Resource Authorization Server Enterprise IdP the person opens the URL on their phone, signs in, and approves POST /device_authorization client assertion + DPoP proof + scope + resource= device_code, user_code, verification_uri POST /token, polling · authorization_pending until they finish DPoP-bound access token · sub = the person, client_id = the agent plus a refresh token, bound to the same key GET /calendar 200 OK · the log names the person and the software

Full walkthrough: add the human →

No registration step, and no human to go do one. The agent's client ID is a URL the server fetches.

An agent meeting an MCP server for the first time The agent calls a server it has never seen, gets a 401 pointing at protected resource metadata, discovers the authorization server, and starts an authorization request using its own URL as its client ID. The authorization server fetches that URL to render consent. No registration call happens at any point. Agent Resource Authorization Server Enterprise IdP the person approves in a browser GET /mcp, with no credential yet 401 · WWW-Authenticate carrying a resource_metadata pointer GET /.well-known/oauth-protected-resource authorization_servers, scopes_supported, DPoP required POST /par · client_id = https://agent.example/id PKCE, resource=, and no registration call at any point fetches the client ID URL, validates the document, and renders consent naming the client and its hostname code + iss (RFC 9207, the defense against mix-up) POST /token · code + PKCE verifier + DPoP proof DPoP-bound token, audience-restricted to this MCP server GET /mcp · the call it was trying to make seven steps ago 200 OK

Full walkthrough: bootstrap from a URL →

Consent about the act, not the category. The request carries the payee and the amount; the token carries what was granted.

An authorization request carrying authorization details The agent pushes the request, including structured authorization details naming the payee, amount, date and source account. Consent is rendered from the pushed request rather than from a scope name. The token echoes the details that were actually granted, and the API compares the request body against them. Agent Resource Authorization Server Enterprise IdP consent renders the payee, the amount, the account. Not a scope name. POST /par · authorization_details: payee, amount, date, source account pushed, so nothing in a browser can modify it on the way to consent request_uri GET /authorize?request_uri=… code POST /token the granted authorization_details, echoed back. They may be narrower than what was asked for. POST /payments 200 OK · the body was compared against the token's details first

Full walkthrough: get specific →

One invocation, challenged. A person approves those exact parameters, and the retry is checked against them.

A transaction authorization challenge, resolved asynchronously The API rejects a refund that is above the limit with a signed challenge describing that one operation. The agent relays the challenge to the authorization server, which resolves it, works out who may approve, and pushes a prompt. The agent polls. The token it eventually gets describes this refund and nothing else. Agent Resource Authorization Server Enterprise IdP the approver is pushed a prompt and approves these exact parameters POST /refunds · £900, with a perfectly valid token 401 · a signed challenge describing the operation: this refund, this order, this amount, this customer POST /token · relays the challenge. The agent cannot alter it, because the resource's signature would not survive. resolves the challenge, and learns how to render this authorization_details type to a human POST /token, polling · authorization_pending for six hours, if that is how long the human sleeps a token whose authorization_details describe this refund and nothing else, valid for two minutes POST /refunds · the retry 200 OK · the details match the challenge it issued. The log records who approved.

Full walkthrough: wait for the human →

Two companies, two authorization servers. Each keeps issuing its own tokens, and the user's identity survives.

Identity chaining with an ID-JAG The agent exchanges the user's ID token at the enterprise IdP for an ID-JAG audienced to the storage provider's authorization server. That server validates it against the federated IdP's keys, applies its own policy, and issues a normal local access token. The API validates a token from its own authorization server, as it always has. Agent Resource Authorization Server Enterprise IdP POST /token · token exchange, presenting the user's ID token the agent authenticates as itself while doing this an ID-JAG: aud = the resource's authorization server, sub = the user, act = the agent. Valid for seconds. POST /token · jwt-bearer grant, presenting the ID-JAG a normal, local, DPoP-bound access token, issued after this server checked the audience was itself and applied its own policy GET /documents/… 200 OK · it validated a token from its own AS, as it always has

Full walkthrough: cross the boundary →

client assertion
a JWT the agent signs with its own key, replacing a client secret
DPoP proof
signed per request, over this method and URL, so a stolen token is useless
resource
names the API the token is for, so it can't be replayed at a different one
client_id
a URL the authorization server fetches to find out who is asking
request_uri
a handle for a request already pushed to the authorization server
authorization_details
the operation and its parameters, not a category
challenge
signed by the resource, naming the one call it wants approved
ID-JAG
a short-lived, audience-bound grant that crosses a company boundary

One flow is missing. When a call arrives and fans out into eleven internal service calls, the question stops being how the agent gets in and becomes how the call context travels inward. That's transaction tokens.

The honest version

Software changed. Five assumptions broke.

OAuth was designed around integrations decided by people, in advance, and written down. Agents don't work that way, and the friction everyone is feeling is real.

The old shape

A developer registers their app, gets a client_id, picks scopes, and ships. A user clicks "Connect," sees a consent screen in a browser, and approves. The app holds a token for months.

The new shape

An agent works out which services it needs while it is running. It meets a server nobody registered with, acts at 3am while its user sleeps, calls another agent that calls an API at a different company. None of it was written down in advance.

01

Nobody registered the client

Registration assumes a human filled out a form and got an identifier back. An agent that discovers a server mid-task has no identifier, and neither side wants a manual step mid-conversation.

Closed by Client ID Metadata Documents. The client publishes its own metadata at a URL, and that URL is the client ID.

02

There's no browser, and no one is watching

Consent was designed as a redirect: a person in front of a screen, reading a page. Agents run in terminals, in CI, at night, and the human may be hours away when a decision is needed.

Closed by the device grant, CIBA, and the transaction authorization challenge. Asynchronous approval, with polling, as a first-class flow.

03

"Read your email" is not a permission

Scopes name categories. Agents take specific actions: move this $4,000, delete that repository, email these forty people. A scope can't say which, so the consent is far broader than the act the user would approve.

Closed by Rich Authorization Requests. Structured authorization details describing the actual operation, not the category.

04

A bearer token is a bearer token

The whole security model of a bearer token is "don't let it leak," and agents copy credentials into prompts, logs, sandboxes and third-party runtimes. A leak is good for the token's entire lifetime, on any machine on earth.

Closed by DPoP and mTLS. The token is bound to a key the agent holds, so a stolen copy is inert.

05

By hop four, nobody knows who asked

A request passes through an agent, an MCP server, a partner's API and three internal services. Somewhere the original identity turns into "trust me, I'm the backend," and the log says a service did it.

Closed by Token Exchange, Identity Chaining and Transaction Tokens. The actor chain travels with the request across every hop.

—

Notice what isn't on this list

None is a flaw in OAuth's core idea: a resource owner granting software narrow, revocable, auditable access without handing over a credential. That's exactly what agents need. What broke are the assumptions layered on top, and each has a spec-shaped answer.

The ladder

Seven scenarios, each one piece bigger than the last.

Start with an agent that has no user and just needs to call an API. Add a person, a service nobody registered with, precision, absence, a company boundary, your own internals. Every rung reuses everything below it and adds one idea.

The agent acts as itself

A nightly job summarizes yesterday's support tickets. No user in the loop, and today it authenticates with a long-lived API key in an environment variable.

Adds: an identity that isn't a shared secret. A key the agent holds, and a short-lived token that's useless if it leaks.

Replace the API key

The agent acts for a person

Now the agent reads your calendar, so the API has to know two things at once: which software is asking, and whose data it may touch.

Adds: delegation. A token naming the user and the agent acting for them.

Add the human

The agent meets a service nobody registered with

Mid-conversation, the agent is pointed at an MCP server it has never seen, run by a company that has never heard of it. No portal step, no human free to go do one.

Adds: runtime discovery and registration-free clients. Protected resource metadata, and a client ID you can fetch.

Bootstrap from a URL

The agent says exactly what it's about to do

"This agent may send email on your behalf" is a promise with no edges. What the user wants to approve is "send this draft to these recipients."

Adds: structured intent. Authorization details describing the operation, so consent and the audit record are about the act.

Get specific

Nobody's watching and it needs permission

Forty minutes in, the agent hits the one step that needs a person: a refund above the limit, a production deploy. Its user is asleep, and guessing is worse than failing.

Adds: asynchronous authorization. The resource issues a challenge, a human approves it out-of-band, and the agent waits.

Wait for the human

The work crosses a company boundary

An agent inside your SaaS vendor's product needs a document in your company's storage. The storage API shouldn't have to learn a new token issuer for every vendor you buy.

Adds: identity chaining. A short-lived, audience-bound grant, so each domain keeps issuing its own tokens and the user's identity survives.

Cross the boundary

The request fans out inside your own walls

One agent call becomes eleven internal service calls. The outermost token stops at the edge, but everything downstream still needs to know who it's acting for and under what authorization.

Adds: transaction tokens. A per-request, internally-scoped credential carrying immutable call context through the fan-out.

Follow it inward

Before the first request

Something has to decide what to ask for.

A user says "chase up our overdue invoices." Between that sentence and an authorization request containing an amount and an account number, a decomposition happens. That's the agent's mission, and naming it lets you draw a line around it.

A mission arrives in natural language, broader than any single API call. Turning it into concrete access requests is a planning problem, not a protocol one, and no specification will tell you how.

Which is why the boundary matters. Mission decomposition is performed by the least trustworthy component in the system, the one reading untrusted invoices and web pages. Its output is a plan, a plan is a request, and a request is not an authorization.

01

Don't decompose the whole thing up front

A mission isn't fully knowable at the start, which is what makes it a mission rather than a script. The temptation is to ask for everything it might need, producing the broadest consent request at the moment the user understands least.

Ask late and ask narrowly. Per-resource authorization is cheap when discovery is a 401 and two well-known documents away.

02

Let the resource tell the agent what's askable

Decomposition doesn't have to be a guess. Protected resource metadata publishes the scopes that exist, and a server can publish which authorization details types it accepts, so the agent finds out what a legitimate request looks like instead of inferring it.

Planning becomes a negotiation with the resource rather than an educated guess, which is why RAR metadata and error remediation matters more for agents than anyone else.

03

The plan must not decide when a human is asked

If the agent's own decomposition determines which steps need approval, then whoever controls the agent's input does. That's the whole game, lost in one line of application logic.

The resource sets the bar and issues the challenge; the authorization server enforces it. The agent is a courier, not a judge. See rung 04.

04

Tie the whole mission together

One mission becomes many grants, tokens and calls across several services. If nothing connects them, your audit trail is a pile of unrelated authorizations and "what did this task do" is an afternoon of log archaeology.

A correlation identifier carried through every hop, which is what a transaction token's txn claim is for. See rung 06.

—

The honest part

Decomposition is out of scope for every specification here, and should be: it's application logic, different for every agent. Naming the mission on the wire isn't, and isn't solved. It's on the list below.

The other direction

Sometimes the agent is the one being called.

Every rung above puts the agent on the left of the arrow, asking for access. Flip it: another company's agent invokes yours. Now your agent is the protected resource, making the decisions the APIs above were making.

This needs nothing new. An agent that accepts calls is an OAuth resource server: it publishes protected resource metadata, validates audience-restricted tokens, checks proof of possession, and decides what the caller may ask for. There is one mistake that gets made almost every time.

Don't forward the token you were given

Your agent receives an access token, needs to call three services, and the token is right there. Sending it onward hands every downstream service a credential minted for you, replayable against anything in that token's audience.

Exchange it instead: Token Exchange inside your own domain, identity chaining for a different authorization server, transaction tokens for the internal fan-out. The act chain nests, so "this agent, for that agent, for this person" survives while each hop gets a credential that only works there.

Agent-to-agent isn't a sixth rung. It's the ladder read in both directions: your agent climbing it as a client, standing at the top of it as a resource.

The parts bin

None of this is speculative.

Everything in the seven scenarios is a published RFC or an active IETF draft. These six do the most work; the full reference has the rest.

DPoP RFC 9449

Binds an access token to a key the client holds, so a copied token is worthless. That's what makes a token safe to hand to a runtime you don't fully control.

Protected Resource Metadata RFC 9728

Lets an API declare which authorization server protects it. The discovery step that makes "meet a server you've never seen" possible at all.

Client ID Metadata Document WG draft

A client's ID is an HTTPS URL serving its own metadata. No registration call, no portal, no shared secret, and the identifier means the same thing at every server that fetches it.

Rich Authorization Requests RFC 9396

Replaces a scope string with a structured description of the operation. The difference between "can send mail" and "send this message to these people."

Token Exchange RFC 8693

Trades one token for another, carrying an act claim that names the actor operating on the subject's behalf. Where "the agent, for the user" becomes something a token can say.

Transaction Tokens WG draft

A short-lived token scoped to one call chain inside a trust domain, carrying immutable context so every internal hop decides for itself without re-deriving who asked.

Not a sales pitch

What's actually missing.

A page claiming everything is solved is a page you shouldn't trust. Some things genuinely aren't standardized yet, and the size of the remaining gap is the whole question of whether this needs a new ecosystem or a few more drafts.

  • Naming the individual agent instance

    A client_id names software, not the copy currently running. There's no standard way to put "this is instance abc-123, spawned at 14:02, by this parent" into a token so a resource can act on it or revoke just that one. Naming instances is well-trodden ground outside OAuth; composing it with OAuth is the open work, and AI Agent Authentication and Authorization is where that's happening.

    Closest thing today: DPoP's key thumbprint as a de-facto instance handle, plus a private claim. Interoperable, no. Workable, yes.

  • How deep delegation is allowed to go

    Token Exchange's may_act says who may act for whom. It can't express "this agent may sub-delegate once more and no further," or "this delegation dies with the parent task." Agents calling agents makes that live.

    Closest thing today: nested act chains plus local policy at each authorization server.

  • Showing a person what they're approving

    Rich Authorization Requests express an operation exactly. Nothing standardizes how a server should render an unfamiliar authorization details type, or what to do with one it doesn't understand. Precision the user can't read isn't consent.

    Closest thing today: the RAR metadata and error remediation draft, which is exactly this problem being worked on.

  • Revocation that actually propagates

    "Kill this agent's access everywhere, now" spans an IdP, three authorization servers and a partner. The machinery exists: the Shared Signals Framework, with CAEP and RISC as its profiles. What's missing is deployment. Until subscribing is ordinary, access dies at the speed of the longest token lifetime in the chain.

    Closest thing today: short token lifetimes and the refresh token expiration draft. Blunt, but it works.

  • Naming the mission on the wire

    One objective becomes a dozen authorizations across several services. Nothing standard lets a token say "this was obtained in service of that mission," so you can't revoke a mission, audit one across companies, or ask a server to refuse anything outside one. The planning step is rightly out of scope; the identifier shouldn't be.

    Closest thing today: a transaction token's txn claim inside one trust domain, and a shared request identifier by convention outside it. Works if everyone involved agreed in advance.

  • Vouching for what an agent is, to a stranger

    Inside a platform this works, and it has a name: posture assessment at the moment a credential is issued. The gap is portability. A client ID metadata document tells you what a client claims to be, and posture assessment only convinces the platform that did the assessing. Neither gives an API across the internet anything it can check.

    Closest thing today: attestation-based client authentication and SPIFFE client authentication where the authorization server sits inside the same trust domain, plus reputation on the client ID URL itself.

The shape of the gap

A claim here, a metadata field there, a rendering convention. Draft-sized problems, landing in an ecosystem that already has authorization servers, libraries, audit pipelines and security review. Not that OAuth is perfect for agents, but that the distance from here to what agents need is short, and the distance from zero is not.

Start at the bottom.

The first rung is the one almost everybody is standing on: an agent with an API key in an environment variable. It's also the easiest to fix.