Classic painting used as the article cover
← Back to blog

AUTHORIZATION

Agent Authorization Explained

Authorization is several checks in sequence, not one. The intersection rule, where OAuth scopes stop being enough, how RFC 8693 carries two identities, and the failures that look identical.

•Jul 10, 2026•Updated Oct 13, 2026•15 min
AuthorizationSecurityAgent Operations

TL;DR

Authorization is not one decision. It's several, checked in sequence, and most agent incidents trace back to only one of them ever being checked. A team wires up a login, confirms the agent is who it says it is, and calls that "authorized", but identity only answers who is acting, not what they're allowed to do.

The standards already assume the gap. OAuth 2.1 says plainly that "access tokens are intended to be issued to clients with less privileges than the user granting the access has." AWS builds the same idea into IAM, where a session's permissions are "the intersection of the identity-based policies for the IAM entity" and the session policy.

The common shortcut treats authentication as a stand-in for authorization: verify the agent, assume the rest is fine. A perfectly identified agent with no limit on its reach is exactly as dangerous as an anonymous one, because identity tells you who broke something, not whether they were allowed to.

None of this means locking agents down until they're useless. The opposite failure is just as common and harder to notice: an agent that quietly inherits everything the person it serves can do, looking fine right up until the day it uses that inherited reach.

What holds up is layered. A request clears identity, then who it's acting for, then which tool, then which action, then which data, before it runs at all. Design the layers before you design the login screen.

Overview

An agent that only answers questions is limited by what it can say. An agent that can act, retrieving records, calling tools, updating systems, sending messages, is limited by what it's allowed to do, and that limit is what authorization defines.

Most teams building their first agent get identity right and stop there. The agent has a service account, it authenticates cleanly, the logs show which agent connected to which system. That feels like the hard part is done. We don't think it is: knowing precisely which agent reached the data warehouse doesn't help if it was free to export every table once it got there.

Which specific permissions an agent should hold is a policy question, covered in agent permissions best practices. This post is about mechanism.

A system that can only say who acted, and not how far they were allowed to reach, is only half built.

Two Questions, Not One

Authentication and authorization get treated as one step because they usually happen in the same login flow. They aren't the same question, and they don't fail the same way.

Authentication answers who or what is this agent. Authorization answers what is this agent allowed to do. A system can nail the first and skip the second, and from the outside it looks fine right up until the agent does something nobody meant to allow.

OAuth's own vocabulary keeps them apart. RFC 6749 defines a resource owner as "an entity capable of granting access to a protected resource" and a client as "an application making protected resource requests on behalf of the resource owner and with its authorization." The agent is the client in that picture, never the owner, and the grant is the thing that has to be bounded.

Why It's Hard for Agents

Authorization for people is a settled discipline: roles, groups, access reviews. Agents complicate it because they sit between a person and a system, acting continuously, at machine speed, on someone else's behalf.

The trap is inheritance. An agent acting for a user is not the user, and shouldn't quietly absorb everything that user is allowed to do. A finance analyst can post journal entries; their reconciliation agent might read balances and propose an adjustment, never post one. A data engineer can query production directly; their analytics agent gets a read replica with sensitive fields stripped out. In both cases the human has more authority than the agent working in their name, on purpose.

That asymmetry is what OAuth was built for. The OAuth 2.1 draft states it outright: "Access tokens are intended to be issued to clients with less privileges than the user granting the access has." An agent handed the user's full authority has thrown away the one property the token was supposed to have.

NIST SP 800-207, published August 2020, makes the same point from the network side. Its third zero-trust tenet grants access "on a per-session basis" and adds that "authentication and authorization to one resource will not automatically grant access to a different resource." An agent that chains four tools together is four sessions, not one.

The Intersection Rule

An action is allowed only when the user and the agent are separately permitted to take it. Not one or the other. Both. The taxonomy calls this aact:authorization-model/intersection-of-agent-and-user.

allow(action) = permitted_for(user) AND permitted_for(agent)

# Procurement example
user (buyer):  can create POs, can approve POs up to $250k
agent:         can create POs, cannot approve any PO

create_po($40k)   -> user: yes,  agent: yes   -> ALLOWED
approve_po($40k)  -> user: yes,  agent: no    -> BLOCKED, needs a person

This is not a novel idea, and that is the best argument for it. AWS IAM has shipped it for years under two names. Session policies, passed when a role is assumed, produce a session whose permissions are "the intersection of the identity-based policies for the IAM entity (user or role) used to create the session and the session policies," and the docs add that they "limit permissions for a created session, but do not grant permissions." A permissions boundary does the same thing structurally: with one attached, "the entity can perform only the actions that are allowed by both its identity-based policies and its permissions boundaries." Read the agent as the session and the user as the entity, and the mapping is exact.

Two properties come with the pattern. It fails closed: a marketer may personally hold publish rights, but if their campaign agent's own grant stops at drafting, the campaign waits for a person regardless. And denial wins over grant. AWS is explicit that "an explicit deny in any of these policies overrides the allow," and Google Cloud puts the ordering in the evaluation path: "IAM always checks relevant deny policies before checking relevant allow policies."

An intersection you cannot state as code is not an intersection. If the agent's ceiling lives in a prompt, a config comment, or somebody's memory of the design review, the effective permission set is whatever the user has.

The Layers

A single allow-or-deny flag is too coarse for something that chains tools together. A request clears several checks, each narrower than the last.

no

yes

no

yes

no

yes

no

yes

no

yes

yes

no

agent requests:
close ticket #4021

identity known
and governed?

blocked

acting for whom?

can it reach
the ticketing tool?

can it close,
not just read?

is this ticket
in its scope?

conditions met?
time, severity

does this action
need a person?

waits for
a person

action runs

Figure 1 — A request has to clear every layer before it runs. One denial anywhere in the chain stops it.

In order: agent identity confirms this is a recognized agent rather than a borrowed credential. User context establishes who it's acting for right now, since the same agent serving a junior rep and a team lead may resolve to different limits. Tool access decides which systems it can reach at all. Action permissions narrow that further: reaching the ticketing system is not the same as being allowed to close, reassign or delete a ticket in it. Data boundaries scope which records it can see. Conditions restrict a permission to a time window, a transaction size, an active incident.

We think the layer people skip most often is conditions, because it requires thinking about when a permission should hold rather than what it covers. NIST's fourth zero-trust tenet is exactly this: "access to resources is determined by dynamic policy," with inputs including client identity, the requesting asset's state, and behavioural attributes. A grant that ignores time, volume and context will eventually be used at a moment nobody would have approved.

Where Scopes Run Out

OAuth scope is the layer most teams reach for, and it carries less than people assume. On its own it is aact:authorization-model/scope-at-grant: fixed when the credential is issued, never rechecked per action. RFC 6749 section 3.3 leaves it loose: "the authorization server MAY fully or partially ignore the scope requested by the client," and when the granted scope differs from the request the server "MUST include the 'scope' response parameter to inform the client of the actual scope granted." The client does not get what it asked for. It gets what it was given, and it has to read the response to find out which.

The deeper limit is expressiveness. RFC 9396, Rich Authorization Requests, published in May 2023, opens by saying the scope parameter "is not sufficient to specify fine-grained authorization requirements, such as 'please let me transfer an amount of 45 Euros to Merchant A' or 'please give me read access to directory A and write access to file X.'" That is the agent problem stated four years early. mail.send is a scope. "May send to addresses inside the company, up to twenty recipients, excluding the all-staff list" is not, and no amount of naming discipline turns one into the other.

RAR's answer is a structured authorization_details array, where each entry carries a type plus fields the RFC defines for reuse: actions, "the kinds of actions to be taken at the resource"; locations, "typically URIs identifying the location of the RS"; and datatypes, "the kinds of data being requested from the resource."

json
{
  "authorization_details": [
    {
      "type": "ticketing_api",
      "actions": ["read", "comment", "close"],
      "locations": ["https://tickets.example.com/queues/tier1"],
      "datatypes": ["ticket", "attachment"]
    }
  ]
}

Whether you adopt RAR or model the same shape in your own gateway matters less than accepting it: an agent permission is a verb plus a place plus a data class, and one flat string cannot hold three.

Carrying Two Identities

If both parties have to be checked, both have to arrive at the check. RFC 8693, OAuth 2.0 Token Exchange, gives that a wire format, and its most useful contribution is a distinction people blur constantly.

Under impersonation, "principal A is given all the rights that B has within some defined rights context and is indistinguishable from B in that context." Under delegation, "principal A still has its own identity separate from B, and it is explicitly understood that while B may have delegated some of its rights to A, any actions taken are being taken by A representing B." An agent running as the user is impersonation, and it destroys the intersection rule by construction. No second identity is left to check. The other shape is aact:delegation-model/on-behalf-of-user.

Section 4.1 describes the act claim as a way "to express that delegation has occurred and identify the acting party to whom authority has been delegated," with nested claims recording a chain and the outermost one representing the current actor. Section 4.4 adds may_act, which "makes a statement that one party is authorized to become the actor and act on behalf of another party."

json
{
  "iss": "https://idp.example.com",
  "sub": "user:a.chen",
  "aud": "https://tickets.example.com",
  "scope": "tickets:read tickets:close",
  "act": { "sub": "agent:tier1-triage" }
}
Ticketing APIGatewayAgentTicketing APIGatewayAgenttool call (agent id + acting user)intersect user scopes with agent scopescheck conditions, data boundaryrequest, audience-bound token403 insufficient_scope (if short)result or denial + reason

Gateway Gateway intersects user scopes with agent scopes Gateway checks conditions and data boundary Gateway --audience-bound request--> Ticketing API API may answer 403 insufficient_scope Gateway returns the result, or a denial with a reason, to the agent The agent never holds a credential and never sees a token it could reuse elsewhere. -->

Figure 2 — Two identities on one request. The agent's own grant is what makes the gateway's check meaningful.

The Model Context Protocol specification, revision 2026-07-28, closes the matching hole on the server side. An MCP server acts as an OAuth 2.1 resource server and "MUST validate that access tokens were issued specifically for them as the intended audience," and servers "MUST NOT accept or transit any other tokens." That is the confused-deputy defence: without it, a token minted for one tool can be replayed against another by anything that can reach both.

The same spec is worth copying for its denial semantics. A server short of permission answers 403 with error="insufficient_scope" and a scope parameter naming the minimum scopes needed, and it "SHOULD include all scopes required for the current operation in a single challenge" rather than dribbling them out one retry at a time. A denial that names what was missing is the difference between a client that can recover and one that loops.

Choosing a Model

Underneath the layers sits a modelling choice. The three candidates fail in different places.

ModelDecides onGood atWhere it breaks for agents
RBACA role assigned to the principalCoarse tool access, easy to reviewConditions and per-record scope. NIST's ABAC guide notes that ad hoc, thinly populated roles lead to "role explosion"
ABACAttributes of subject, object, environmentTime windows, amount limits, data class, regionAttributes must be populated and fresh; a stale one is a silent grant
ReBACA relationship graph"May act on tickets in queues its user owns", per-object checksGraph freshness, and a check on every object in a list

Table 1 — Three authorization models against the agent workload. Most production systems end up with RBAC for tool access and one of the other two for records.

NIST SP 800-162, published January 2014, defines ABAC as access "granted or denied based on assigned attributes of the subject, assigned attributes of the object, environment conditions, and a set of policies that are specified in terms of those attributes and conditions." That is the vocabulary the conditions layer needs, and it is why we think most teams land on a hybrid.

Before writing any of it yourself, price the alternative. Cedar, OpenFGA and Open Policy Agent are mature, permissively licensed, and have already solved decision caching, evaluation ordering and a test harness for rules. Build in-house only the agent-specific piece, the intersection and the two identities, because none of them models that for you.

How It Fails

These look alike from a distance and have opposite fixes.

FailureWhat you seeHow to tell it apart
Inherited authorityAgent succeeds at something nobody granted it, with no denial in the logsThe call carries one identity, the user's. The agent's own grant was never evaluated
Confused deputyAgent reaches a system it was never connected toA token minted for tool A accepted by tool B. Audience validation is missing, or the gateway forwards tokens verbatim
Scope creepPermission list grows monotonically, nothing removedCompare granted scopes against scopes exercised in the last 30 days. The gap is the blast radius
Stale attributeA condition that should deny keeps allowingThe attribute has not updated since a reorg. ABAC's characteristic failure
Coarse denialAgent retries forever, or gives up entirelyA 401 or a bare 403 with no scope hint. Nothing told the caller what was missing
Ignored partial grantAgent acts as though it holds scopes it was refusedNobody read the scope in the token response. RFC 6749 lets a server grant less than was asked

Table 2 — Six authorization failures, and the signal that separates each from its neighbours.

Two of those rows are worth instrumenting rather than investigating. Log the identity count on every call: a request carrying one identity where two were expected is the inherited-authority failure before it becomes an incident. And log granted-but-unused scope per agent per month, because that number only moves in one direction unless somebody looks at it.

Least Privilege Is the Default

Give an agent only the minimum it needs, nothing held "just in case." Broad access is a quiet temptation during development, because full admin removes friction while you iterate. An over-permissioned agent doesn't just have capability sitting unused; it has a wider blast radius the moment a prompt gets manipulated or the model reasons its way into something nobody intended.

The MCP authorization spec builds the incremental version into its flow: clients "SHOULD follow the principle of least privilege by requesting only the scopes necessary for their intended operations," with scopes_supported representing "the minimal set of scopes necessary for basic functionality" and anything further requested through step-up authorization when an operation needs it. That beats requesting everything at install time in case it comes up.

Authorization Without a Record Is Half a Control

Knowing what an agent was allowed to do is half of it. You also need to know what it did, and whether the two matched.

Every decision the authorization layer makes belongs in the record next to the action: what was requested, which permission applied, whether it was allowed or blocked, and which identity each half of the intersection resolved to. NIST's zero-trust model separates the component that decides from the component that carries the decision out, and notes that the deciding one "makes and logs the decision (as approved, or denied)." The log is part of the control, not a report about it.

Without that link, authorization is a claim about how the system is supposed to work. With it, you can answer the question that comes up in an incident: what was allowed, and did anything happen outside the lines. OWASP's Agentic AI: Threats and Mitigations, published 17 February 2025 as the first guide from its Agentic Security Initiative, is a reasonable starting point on the threats this record has to reconstruct. Log design is covered in audit logging best practices.

Where Fabriq Fits

Agentic Fabriq implements the intersection rule as a default rather than a configuration option. An agent's effective tool list is the intersection of the user's scopes and the agent's own, permissions are default-deny across org, team and member with per-user overrides, and action-level permissions are enforced in the backend rather than advertised in a tool description. Every call carries two identities into an audit record. Credentials sit in a vault and are attached server-side at the moment of the call, so the agent never handles the secret and has nothing to replay elsewhere.

What it does not do is reach into the provider for you. Clearing a vault copy is not the same act as invalidating a grant upstream, so revoking at the identity provider or the SaaS application is still yours to do.

Frequently Asked Questions

What is agent authorization? The set of checks that decide whether a specific action an agent is attempting, right now, on behalf of a specific person, is permitted. It is distinct from authentication, which only establishes which agent is calling.

What is the difference between agent authentication and authorization? Authentication answers who is acting. Authorization answers what they may do. A system with perfect authentication and no authorization can tell you exactly which agent exported the customer table and nothing about whether it should have been able to.

Should an AI agent have the same permissions as the user it serves? No, and OAuth assumed as much from the start. The OAuth 2.1 draft says access tokens "are intended to be issued to clients with less privileges than the user granting the access has." An agent is faster, less context-aware and unattended, so its grant should be a subset of the user's.

Is OAuth scope enough to authorize an agent? Rarely. RFC 9396 states that scope "is not sufficient to specify fine-grained authorization requirements", and gives per-amount and per-directory examples. Scope handles which tool. Verb, record and condition need structure underneath it.

RBAC or ABAC for agents? Both, usually. Roles are the cheapest way to answer which tools an agent may reach. Attributes are the only practical way to answer time, amount, region and data class. NIST's ABAC guide warns that pushing the second kind of question into roles produces "role explosion."

Where should the authorization check run? Server-side, at a point the agent cannot bypass, and on every call. A check inside the agent's own prompt or client code is a suggestion, not a control.

The Boundary That Makes Autonomy Usable

Good authorization makes agents safer and easier to scale at once. It gives builders a line to work within and gives security teams a real answer, not a hope, for whether an agent can act outside its limits.

Three things carry most of the weight. State the intersection in code, so a user's authority cannot leak into the agent. Give a permission enough structure to name a verb, a place and a data class. And make every denial say what was missing, so narrowing a grant is a decision rather than an outage.

Authorization is the line between useful automation and autonomy nobody actually decided to grant. Skip it and the question isn't whether an agent oversteps. It's when.

Sources

All URLs read 2026-09-29.

</invoke>