An OAuth scope is a category, not a decision. read:tickets tells you an agent may read tickets in general. It cannot tell you this agent may read this ticket, for this user, once, as part of this workflow. For human apps that gap was tolerable. For agents, where the whole point is that a request carries specific intent through a chain of hops, coarse scopes are the reason the grant an agent holds is almost always broader than the action in front of it.
Rich Authorization Requests, RFC 9396, is the standard that closes that gap. It lets a request express fine-grained authorization requirements as structured data rather than a bag of scope strings. This guide explains what RAR is, why scopes fall short for agents specifically, and how RAR fits with delegation and a policy engine to give each hop a grant that matches what it is actually doing. It builds on our blog on multi-hop delegation for AI agents.
What are Rich Authorization Requests?
Rich Authorization Requests, defined in RFC 9396, is an OAuth extension that lets a client specify fine-grained authorization requirements using an authorization_details parameter, a JSON array of typed objects, instead of, or alongside, coarse scope strings. Each object describes a specific permission with whatever fields that permission type needs, such as the action, the target resource, and any limits. It gives an authorization request the expressiveness of structured data, so a grant can say exactly what it permits rather than naming a broad category.
The shift is from scopes as labels to authorization details as specifications. A scope names a bucket of access. An authorization detail describes a particular action on a particular resource, precisely enough that the grant and the request can actually match.
Why scopes are not enough for agents
RFC 9396 makes the case in one line. Coarse scopes are sufficient for static scenarios and coarse-grained requests, such as give me read access to the resource owner's profile. They are not sufficient to specify fine-grained requirements, such as please let me transfer an amount of forty-five euros to Merchant A. The difference between those two sentences is the difference between a scope and an agent action.
An agent acting for a user is almost never doing something as broad as read the profile. It is doing a specific thing, on a specific resource, often with a specific limit, and often on behalf of a specific person whose identity matters to whether the action is allowed. Encode that as a scope and you have two bad options. Either the scope is broad enough to be safe to grant but too broad to be safe to hold, or you mint thousands of narrow scopes and drown in them. We covered the same failure from the MCP side in MCP authorization standards. Scopes are fixed at token-issue time, and they cannot see the arguments of the call that has not happened yet.
RAR is the standardized way out. The authorization detail can carry the specific action, the specific resource, and the limits, so the grant is shaped like the request rather than like a category the request happens to fall into.
How authorization_details works
Instead of asking for scope=payments, a client sends an authorization_details array. One object in it might describe a payment initiation with a type, an instructed amount and currency, and a creditor. Another might describe read access to a named account. The authorization server understands the types it supports, obtains consent for exactly those details, and issues a token whose granted authorization_details reflect what was actually approved, which may be narrower than what was asked for.
For an agent chain, this is the primitive that lets each grant be minted to fit its hop. When a request is exchanged for the next agent, the authorization details handed forward can be narrowed to what that agent actually needs, rather than passing along the full breadth of the original grant. Combined with the act chain from Token Exchange, you get a token that says who is acting for whom and, precisely, what they are permitted to do at this step.

To make it concrete, compare the two shapes of a request. The scope version says almost nothing.
scope=payments
The rich version says exactly what is being asked for.
authorization_details=[{
"type": "payment_initiation",
"actions": ["initiate"],
"instructedAmount": { "currency": "EUR", "amount": "45.00" },
"creditor": { "name": "Merchant A" }
}]
The second one can be consented to, carried, and checked as the specific thing it is. A downstream hop reading it knows the action is a payment initiation, the amount is forty-five euros, and the recipient is Merchant A, so it can decide against those exact values rather than against a category called payments. The type field is the key. It names a permission shape that the authorization server and the policy both understand, and each type carries whatever fields that permission needs, an amount and creditor for a payment, an account and a date range for a statement read, a path and a mode for a file. RFC 9396 does not fix the set of types. It gives you the envelope to define the ones your domain needs, which is what lets the grant describe a real action instead of approximating it with a scope.
For an agent chain, the practical effect is that the grant handed to each hop can be exactly the size of that hop's job. The assistant might hold a details object covering the whole trip. The flight sub-agent receives one narrowed to a single flight purchase under a specific amount. The precision that scopes force you to throw away, RAR lets you keep and narrow, hop by hop.
RAR expresses the request. Policy decides it.
Now let’s talk about the boundary that keeps this clean, and it is the same one that applies to every token mechanism in a delegation chain. RAR expresses, in structured form, what an agent is asking to be allowed to do. It does not, by itself, decide whether the agent should be allowed to do it. The authorization server can consent to a set of details, and the token can carry them, but the runtime question at each hop, is this specific action, by this agent, for this user, on this resource, allowed right now given everything else that is true, is a policy decision.
That decision belongs in a policy decision point, not in the token. The structured authorization details are a rich input to the decision, which is precisely why they are useful. A policy can read the authorization details, compare them to what the user consented to, check them against the resource and the context, and decide. This is the same reason we describe tokens as authorization decisions worth governing rather than as the decision. RAR makes the request expressive. A policy engine makes it a decision. Keeping those two jobs separate is what stops the token from becoming a place people try to encode business rules that then cannot be changed without reissuing credentials.
For the decision to consume structured details, the policy language has to be expressive too. You want policies that can read a nested authorization detail, reason over its fields, and evaluate conditions against the user, the resource, and the context. Structured, testable, version-controlled policy is the counterpart to a structured, fine-grained request. A coarse policy language paired with RAR wastes the precision the request went to the trouble of carrying.
How Cerbos fits
Cerbos is the policy engine that turns a rich request into a decision. When a hop presents a token carrying authorization_details and an act chain, the service calls the Cerbos PDP with the acting agent, the subject user, the requested authorization details, the resource, and the context. Policies are written in YAML with CEL for conditions, which is expressive enough to read the structured details, compare an amount to a limit, check a resource against the user it belongs to, and decide on the specific action rather than the category.
Because Cerbos speaks the OpenID AuthZEN standard, it takes the subject, action, resource, and context of a request and returns a decision, which maps directly onto what a rich request carries. And because the policy is code, reviewed and tested like any other code, the fine-grained rules that match fine-grained requests are maintainable rather than a sprawl of one-off scopes. The consent side of this is covered in consent that survives delegation.
Scopes were a reasonable tool for a world of human apps asking for broad access. Agents ask for specific actions on specific resources for specific people, and RAR is how the request finally gets specific enough to match. Pair it with a policy engine that can read the detail, and the grant an agent holds stops being a category it might abuse and becomes the exact thing it was allowed to do.
Try Cerbos to see fine-grained, structured authorization for agents in practice, or book a call to talk through your token and policy model with the team.
Go deeper:
- Adding fine-grained authorization for non-human identities (webinar) for the full agent authorization pattern
- A benchmark for authorization (eBook) for finding and fixing authorization gaps
FAQ
Tagged in




