Classic painting used as the article cover
← Back to blog

AUTHORIZATION

Agent Authorization vs. Authentication

401 or 403, invalid_token or insufficient_scope. The two checks an agent request passes through, the error codes that tell them apart, and why an agent needs two identities where a person needs one.

•Jul 15, 2026•Updated Oct 13, 2026•15 min
AuthorizationAuthenticationIdentity

TL;DR

Authentication and authorization are not two settings on one dial. They are two checks, run in a fixed order, and the wire protocol has told them apart for thirty years.

RFC 9110 defines 401 as a request that "lacks valid authentication credentials for the target resource", and 403 as a server that "understood the request but refuses to fulfill it". RFC 6750 splits the OAuth layer the same way: invalid_token returns 401, insufficient_scope returns 403.

The common failure is not that engineers cannot recite those codes. It is that agent platforms collapse them into one log line reading "access denied", so the incident review starts from a category instead of a cause, and the wrong layer gets hardened.

This is not an argument that agents need a new access-control model. They need the existing one applied twice, because an agent carries two principals where a person carries one.

The check worth running today: grep your agent gateway for the place it turns a downstream 401 or 403 into a generic error, and stop it doing that.

Overview

HTTP settled this distinction decades ago, with two status codes most engineers can recite. Mix them up in a design review and someone corrects you on the spot. Agent platforms have not inherited that reflex, even where the codes themselves are emitted correctly.

Ask why a particular agent request got stopped and the answer routinely comes back as one flat phrase in the incident channel. That phrase covers two unrelated situations. Either the platform never confirmed which agent was asking, or it confirmed the agent precisely and refused anyway because the action sat outside what that agent may do. Those are different problems with different fixes, and folding them into one incident category is how the wrong one gets patched.

We think the collapse costs more than people expect. An agent whose identity cannot be pinned down is a different liability from an agent whose credentials check out fine but whose scope was set too generously by someone in a hurry. Treat them interchangeably and you harden the layer that was never weak.

What follows draws the line at the level of the wire: the status codes, the error strings, and the claims each check reads. Then it works through the thing an agent needs that a person does not, which is two identities on one request. Sizing those permissions in the first place is a separate subject, covered in agent permissions best practices.

The line that matters: authentication answers "is this actor who it claims to be." Authorization answers "should this actor do this, right now." An agent can pass one and fail the other, and from a support ticket the two failures look identical.

Two Questions, in Order

The order is not arbitrary. A system authenticates first and authorizes second, because authorization is a lookup keyed on identity and you cannot look up permissions for an actor you have not confirmed. Reverse the order and you are checking permissions against a guess.

NIST states the boundary in the guideline that defines digital authentication. Authentication is "the process of determining the validity of one or more authenticators used to claim a digital identity", and having done that, "authentication does not determine the claimant's authorizations or access privileges; this is a separate decision." Separate decision, separate machinery, separate failure.

NIST's zero trust work then adds the part that matters for agents specifically: "all resource authentication and authorization are dynamic and strictly enforced before access is allowed", with access granted "on a per-session basis." Both checks, every request, not once at connection time.

For agents that ordering catches something people rarely face with humans. Identity is not fixed the way a login session is. An agent might act as itself, or for a specific employee, or be invoked by another agent acting for both. Authentication has to resolve which of those is true before authorization can mean anything.

Permission CheckIdentity StoreGatewayAgentPermission CheckIdentity StoreGatewayAgentrequest + credentialverify credentialidentity confirmed (or 401)is this identity allowed to do this?allow (or 403)result

Gateway Gateway --verify--> Identity Store --confirmed, or 401--> Gateway Gateway --check--> Permission Check --allow, or 403--> Gateway Gateway --result--> Agent Authentication runs first and can fail with a 401. Authorization runs second, on a confirmed identity, and can fail with a 403. -->

Figure 1 — Authentication and authorization as two sequential checks on the same request. Each fails on its own, and each failure has its own name.

Get identity wrong at the first step and the permission check that follows answers the wrong question correctly.

Authentication: Proving the Actor

Take a marketing automation that pulls campaign performance and pushes spend adjustments into an ad platform. Authentication is what lets the ad platform say, with confidence, that this request came from the campaign-optimization agent owned by the growth team, presenting a credential unique to it. Swap in any other agent's credential and the request looks identical. That is the failure mode worth naming: a known credential says nothing about who is currently holding it.

The spec makes the server's obligation concrete. Under RFC 9110, a 401 "indicates that the request has not been applied because it lacks valid authentication credentials for the target resource", and a server generating one "MUST send at least one WWW-Authenticate header field." That header is not decoration. RFC 9728 extends it with a resource_metadata parameter carrying "the URL of the protected resource metadata", which is how a client learns which authorization server to go to. MCP makes that mandatory: servers "MUST use the HTTP header WWW-Authenticate when returning a 401 Unauthorized" to point at their metadata.

Strong agent authentication rests on three things. Each agent gets its own identity, aact:identity-model/per-agent-identity, rather than a shared service account or a borrowed human login. Its credentials belong to it alone, and the IETF's agent authentication draft wants those credentials to "provide a cryptographic binding to the agent identifier", which rules out a shared key any process can read. And the identity has to be verifiable at request time and traceable to a specific owner.

NIST's assurance levels are a yardstick for how hard that proof is. AAL1 gives "some assurance that the claimant controls an authenticator", AAL2 requires "two different authentication factors", and AAL3 rests on "proof of possession of a key through a cryptographic protocol." An X.509 credential bound to an agent's identifier is closer to the third than a bearer token in an environment variable is.

Authentication only answers who. Mistaking a strong authentication story for a finished access story is most of what goes wrong here.

Authorization: Bounding the Action

Take a registered legal-intake agent, fully authenticated, so the enterprise knows exactly which agent is asking. That says nothing about whether it may delete a matter, export a privileged document, or only summarise an intake form. Authorization maps a confirmed identity onto a bounded set of permissions, and unlike authentication the answer depends on more than identity: the action, the resource it targets, sometimes the size of the request.

At the OAuth layer this check has its own vocabulary. RFC 6750 defines insufficient_scope as the case where "the request requires higher privileges than provided by the access token", and assigns it HTTP 403. RFC 9068 adds an authorization check that people forget is one: a resource server must verify that the aud claim names it, and "the JWT access token MUST be rejected if aud does not contain a resource indicator of the current resource server as a valid audience." A perfectly authentic token issued for a different service is an authorization failure, not an authentication one.

MCP turns the 403 into something a client can act on. The server returns the scopes the operation needs, so the agent can run a step-up authorization instead of guessing:

http
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
                         scope="files:write",
                         resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
                         error_description="File write permission required for this operation"

A registered HR onboarding agent might read provisioning templates but never payroll. A registered data-platform agent might run read queries but never alter a schema. A registered procurement agent might draft purchase orders under a threshold and route anything larger to a person. In every case the identity is settled and the answer is still conditional. That conditionality is the whole job.

Where They Actually Differ

AuthenticationAuthorization
Question it answersIs this actor who it claims to be?Should this actor do this, right now?
What it checksA credential against an identity recordAn identity and an action against a permission set
Typical mechanismX.509 or JWT credentials, mutual TLS, signed assertionsScopes, roles, resource-level rules, aud validation
HTTP failure401, with a mandatory WWW-Authenticate header403, with error="insufficient_scope" under RFC 6750
OAuth error stringinvalid_token, invalid_clientinsufficient_scope
Fixing it meansIssuing, rotating or re-binding a credentialNarrowing or widening a scope, or fixing the audience

Table 1 — The rows never trade places. Rotating a credential does nothing for an over-broad scope, and narrowing a scope does nothing for a shared credential three agents can present.

We would start every agent incident review by asking which row it belongs to, because the fix lives on that row and nowhere else.

Reading the Failure

The reason that question is answerable at all is that the specs assigned distinct strings to distinct causes. Most agent platforms throw those strings away and keep the status code, which is the single most expensive line of error handling in this space.

What you seeWhat actually failedSpecHow to confirm
401 + invalid_tokenThe access token is "expired, revoked, malformed, or invalid for other reasons"RFC 6750 §3.1Decode it. Check exp first, then whether the issuer still recognises it
401 with no WWW-AuthenticateA non-conforming server. You cannot tell which challenge scheme it wantsRFC 9110 §15.5.2The header is a MUST. Its absence is a bug to report, not a clue
403 + insufficient_scopeIdentity fine, scope too narrow for this operationRFC 6750 §3.1The scope parameter in the challenge names exactly what is missing
403 with no error stringIdentity fine, refused for a reason the server will not nameRFC 9110 §15.5.4Resource-level rules or tenancy. Compare against the same call for a different object
401 rejecting a valid-looking tokenWrong audience. The token was minted for another resourceRFC 9068Read aud. It names a service that is not the one you called
400 + invalid_client at the token endpoint"Client authentication failed", so the agent never got a token at allRFC 6749 §5.2Fails for every connection at once, usually from a deploy or secret rotation
400 + invalid_requestThe request itself is malformed, repeats a parameter, or sends the token two waysRFC 6750 §3.1Reproduce with curl. It is almost always a client bug

Table 2 — Seven failures behind two status codes. The middle column is the fix; the right column is how you get there without guessing.

Two of these look alike and are not. A 401 from an expired token and a 401 from a wrong audience both carry a valid-looking JWT, and the difference is entirely in aud. The first is fixed by refreshing. The second is fixed by sending the resource parameter that RFC 8707 defines and MCP requires, so the authorization server mints a token bound to the service you are actually calling.

Blast radius is the other free signal. Under RFC 6749 invalid_client means "client authentication failed", and a bad client secret fails every connection at once while a dead grant fails one. That ratio is often faster to read than any log line.

Log the error string next to the status code. Two codes cannot describe seven causes, and the provider already told you which one it was.

Two Identities, One Request

Agents rarely act purely as themselves. A scheduling agent updates a calendar for an employee. An expense agent files a report for a traveller. The tempting shortcut is to let the agent log in as the person, inherit the session and the access, and disappear from the record.

RFC 8693 named that shortcut. Under impersonation, "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." Indistinguishable is the cost, and it is paid in the audit trail.

The alternative keeps both identities live on every request, and the claim layout for it is now written down. RFC 9068 requires client_id in every JWT access token and puts the resource owner in sub for user-delegated grants. The IETF's agent authentication draft applies that directly: "the access token includes the Agent identity as the client_id claim", and "when the Agent is acting on-behalf of another User or System, the User or System identifier is conveyed in the sub claim." For multi-hop delegation RFC 8693 adds the act claim, which identifies "the acting party to whom authority has been delegated" and nests so that "the outermost act claim represents the current actor while nested act claims represent prior actors".

That structure produces the intersection people usually describe as a policy. Microsoft's delegated permission model states it as a fact about the mechanism: an app "can't access anything the signed-in user couldn't access", and the privileges it has acting for someone are determined by the app's grant and the user's own permissions. The taxonomy files that as aact:authorization-model/intersection-of-agent-and-user.

no

yes

no

yes

no

yes

no

yes

request arrives

credential
verifiable?

401 invalid_token
+ WWW-Authenticate

aud names
this server?

401, wrong audience
resend with resource param

scope covers
this action?

403 insufficient_scope
+ required scopes

acting user
allowed too?

403, intersection empty

proceed, log both identities

Figure 2 — The four gates an agent request passes. The first two are authentication, the last two authorization, and only the last one has no equivalent for a human request.

One line captures the result:

Agent A attempted to update Record B
  on behalf of User C
  within Permission D

That carries authentication (Agent A), the delegation (User C) and authorization (Permission D). When an action can be described that completely, an incident review is a lookup. When it cannot, it is a guess dressed as a finding.

A Password Reset, Traced Twice

Take an IT operations agent that resets passwords and unlocks accounts across a corporate directory. When it connects it presents a credential unique to itself, bound cryptographically to its own identifier. Authentication confirms this is the account-recovery agent and no other.

When a help-desk technician routes a ticket to it, the agent acts on that technician's behalf and the technician's identity travels with the request. Ask it to unlock a standard user account and authorization allows it, because that action sits inside the agent's scope regardless of which technician asked. Ask it to reset a domain administrator's credentials and the call comes back as a 403, because the action sits outside the agent's scope no matter who is asking. Same agent. Same authentication. Opposite outcomes, because authorization answers a different question each time.

The intersection adds a third outcome that is easy to miss. A technician who can unlock accounts in one region but not another asks the agent to unlock an account in the second. The agent's scope covers it. The technician's does not. The correct answer is a refusal, and it is a refusal that has nothing to do with the agent's own permissions. Systems that check only the agent's grant return a success here, and that is the side door.

Every one of those events lands in a trail naming the agent, the technician and the permission exercised. Months later, when someone asks why an account was unlocked at two in the morning, there is an answer instead of a shrug.

Where Fabriq Fits

Agentic Fabriq carries both identities on every call, the agent and the acting user, and makes an agent's effective tool list the intersection of the user's scopes and the agent's, which is the fourth gate in Figure 2 enforced in the backend rather than per integration. Credentials sit in a vault and are attached server-side at the moment of the call, so the agent never handles the secret, and each call lands in an audit record naming both principals.

It does not revoke a grant at the provider. Clearing our vault copy of a credential is a deletion on our side, and the call to the provider's revocation endpoint remains yours.

Frequently Asked Questions

What is the difference between authentication and authorization for AI agents? Authentication proves which agent is asking. Authorization decides whether that agent, acting for that person, may take this action on this resource. NIST puts the boundary plainly: authentication "does not determine the claimant's authorizations or access privileges; this is a separate decision."

Is 401 authentication or authorization? Authentication. RFC 9110 defines 401 as a request that "lacks valid authentication credentials for the target resource", and a 401 response "MUST send at least one WWW-Authenticate header field". The name of the status code is a historical misnomer.

When should an agent gateway return 403 instead of 401? When the identity is confirmed and the action is refused. RFC 6750 assigns 403 to insufficient_scope, "the request requires higher privileges than provided by the access token", and MCP tells servers to include the required scopes in the challenge so the client can step up.

Why does my agent get a 401 with a token that has not expired? Check aud. Under RFC 9068 a resource server must reject a token whose audience does not name it, so a token minted for another service fails even while perfectly valid. The fix is sending the RFC 8707 resource parameter when requesting it, not refreshing.

Can an agent authenticate as the user it works for? It can, and RFC 8693 calls it impersonation, defining it as the agent being "indistinguishable from B in that context". That indistinguishability is the reason not to. Delegation keeps the agent's own identity alongside the user's.

How do two identities fit in one access token? The agent goes in client_id and the acting user in sub, which is what RFC 9068 requires and what the IETF's agent authentication draft specifies. Multi-hop delegation adds act, which nests so the outermost value is the current actor and the inner ones are prior actors.

Does an agent need its own credentials if the user already logged in? Yes, or you have no way to tell which agent acted. The IETF draft requires credentials that "provide a cryptographic binding to the agent identifier", which a borrowed session cannot provide.

What should an access-denied log line contain? The status code, the OAuth error string, the agent identifier, the acting user, the resource and the scope that was missing. Keeping only the code turns seven distinct causes into one unactionable category.

Conclusion

Knowing who the agent is and knowing what it may do are different questions, checked by different mechanisms, in a fixed order, and the protocols have carried that separation since long before agents existed. What agents added is a second principal on the same request, which turns the second check into an intersection rather than a lookup.

Keep an agent's identity distinct from the identity of the person it acts for. Give it its own narrow permissions. Preserve the relationship between the two in sub and client_id, record both, and stop flattening the error strings the providers already give you. We think that is the entire discipline, and the fact that it fits in a paragraph is exactly why teams keep skipping it.

An agent needs its actor proven and its action allowed, and neither check substitutes for the other. Both have to run against an identity it cannot borrow and cannot share.

Sources

All URLs read 2026-09-29.