Agent infrastructure
Platform integration
AI agent authorization deep-dive: why traditional API auth breaks, the four patterns teams use, delegated tokens, scope design, and production failure modes.

AI agent authorization is the question most agent projects fail to answer honestly until security review. The article that says "use OAuth" is not the article you need. What you need is a working model of how permission decisions get made, who they're made on behalf of, when they're made, and what happens when the model asks for something the user can't do.
Our view: authorization for AI agents is not an auth problem, it's a runtime boundary problem. The token is the easy part. The hard parts are delegated identity flowing through every tool call, scopes tight enough to survive an audit, and enforcement that happens at the tool boundary rather than the prompt. This deep-dive walks through five sections: why the old model breaks, the four patterns teams actually use, how delegated authorization works end to end, permission scopes for agents, and the failure modes that show up in production.
The authorization model most SaaS platforms ship with was built for a caller that behaves. A human developer reads the docs, requests the scopes they need, handles the 403, and files a support ticket if something's off. Their client code is deterministic. When it asks to delete a record, someone wrote that line.
Agents don't behave that way. The model decides which tool to call based on user intent, prior context, and the tool descriptions it's been given. That decision is probabilistic. It might call delete_customer when it meant archive_customer. It might chain three tools in a sequence no human designed. It might retry a failed call with slightly different arguments. The authorization layer has to hold against a caller that's making choices you didn't script.
Three assumptions in traditional API authorization break under this load. First, the assumption that the client and the user are effectively the same principal — fine when a developer builds an integration for their own use, wrong when an agent acts on behalf of a user whose permissions differ from the developer's. Second, the assumption that scopes are static and coarse — a read:contacts scope that made sense for a CRM integration is far too broad when the agent runs across a portfolio of tools. Third, the assumption that authorization decisions happen at token issuance — the agent's context changes mid-conversation, and a scope granted at session start may not still be appropriate three turns in.
The result is what you see in most stalled agent projects: the agent works in demo, passes security review only with a service account carve-out, and dies at the last mile when someone asks who actually did what. We wrote about the identity side of this in zero trust for AI agents — the short version is that a shared service account collapses the audit trail and the permission model at the same time.
Most teams end up on one of four patterns. They're not equally good, but each one exists because it solves some problem cheaply. Here's how they compare on the dimensions that matter.
Shared service account. The agent authenticates once, as itself, with permissions that are the superset of every action any user might need. This is the pattern that gets an agent working in a week and killed in security review. It works in internal tooling where every user has the same permissions anyway; it does not work in multi-tenant SaaS where an agent acting on User A's behalf could read User B's data because the service account can see everything.
API key per agent. Each agent instance gets its own long-lived credential. Better than shared service accounts for isolation between agents, but still terrible for identity — the agent is authenticated, but the human whose intent triggered the call isn't. The audit log shows the agent did it. Regulated industries don't accept this.
User-delegated OAuth. The agent holds an OAuth token issued on behalf of the user, with scopes granted at consent time. Every tool call runs as that user with those scopes. This is where most production-grade agent projects land — it satisfies most security reviews and gives you a real audit trail. The failure mode is long-lived refresh tokens: a compromised agent can act as any of its users for as long as the refresh token is valid.
Just-in-time delegated tokens. Short-lived tokens minted for each tool call, narrower than the user's full scope, tied to the specific action being taken. This is where the boundary actually holds under audit. It's also where most teams underestimate the implementation cost — token exchange (RFC 8693), DPoP for sender-constrained tokens, and the runtime to hold all this together isn't a weekend project. We laid out the mechanics in our just-in-time tokens guide.
The honest ranking: if you're building an internal agent for a homogeneous user population, user-delegated OAuth is enough. If your agent touches customer data across tenants, just-in-time delegated tokens are the boundary that will survive first contact with a real security review.
The protocol-level story is straightforward. The complications live in the joins. Here's the sequence, end to end, for a delegated authorization flow that holds up under review.
A user asks an agent to do something. The agent's runtime — not the model — resolves the tool it needs to call. Before invoking that tool, the runtime performs a token exchange: it takes the user's session token and requests a new, short-lived token scoped to exactly the action about to be performed. The authorization server checks that the user actually has the permission being requested, that the agent is allowed to request delegation on the user's behalf, and that the scope being asked for is a valid subset of the user's grants. If any of those fail, the exchange fails and the tool call never happens.
The returned token carries three things that matter: the user's identity (as sub), the delegation chain (typically as an act claim showing the agent acted on the user's behalf), and the narrow scope for this specific action. The tool call goes out with that token. The receiving API checks the scope, executes as the user, logs the call with both the user and the agent identities preserved, and returns.
Where teams get this wrong is in three places. First, they skip token exchange and reuse the user's session token directly — which works but gives the agent's tool call the user's full scope rather than the narrow scope it needed. Second, they exchange once at session start and cache the exchanged token — which means the audit log can't tell which specific action the exchange was for. Third, they log the tool call but not the delegation chain — which means at audit time you can prove the user's account did it, but not that the agent was the intermediary.
The MCP spec's authorization model (via OAuth 2.1) gets token issuance right — mandating PKCE, RFC 9728 Protected Resource Metadata, RFC 8414 Authorization Server Metadata, and RFC 8707 Resource Indicators, and forbidding token pass-through — but it leaves multi-hop delegation chain semantics to implementers. It gestures at token exchange for downstream calls without prescribing how the chain gets preserved. We covered the specifics in MCP server authentication. If you're building a general-purpose tools layer, the delegation model is on you.
Scopes designed for human developers are the wrong granularity for agents. Human developers request broad scopes upfront because they know their whole integration and can reason about what it needs. Agents don't know their whole integration — they choose tools at runtime based on user intent — so scopes granted at consent time either have to be broad (bad for least privilege) or the runtime has to re-scope per call.
The pattern that works: two tiers of scopes.
At consent time, the user grants capability scopes — coarse-grained permissions that describe categories of action. Something like contacts:read, contacts:write, messages:send. These are what the user sees in the consent screen. They should be intelligible to a non-technical user: "the agent may read your contacts, may send messages on your behalf."
At tool-call time, the runtime requests action scopes — fine-grained permissions that describe the specific operation. contacts:read:id=42, messages:send:to=user@example.com. These are minted per call, narrower than any single capability scope, and never exposed to the user directly. Their job is to shrink the blast radius if the token leaks.
This two-tier model does three things at once. It keeps consent screens legible. It gives least-privilege enforcement at the call boundary. And it gives you audit records that show not just "agent had permission to send messages" but "agent sent this specific message to this specific recipient with this specific scope." We wrote about the runtime side of this in least privilege for AI agents — the design principle is that least privilege is a runtime property, not a policy declaration.
The hard case is scopes for tools the user didn't know about at consent time. If a new tool ships that maps to an existing capability scope, is the user's prior consent still valid? Legally maybe; ethically usually not. The pattern most teams settle on is versioned capability scopes: contacts:read:v1 covers the tools that existed at consent time; new tools that expand the scope require re-consent. Awkward but honest.
Agent authorization failures rarely announce themselves. Here are the ones we see most often and the signal you'll notice first.
Scope creep across tool selection. The agent chains tools in ways the consent screen didn't anticipate. User grants contacts:read and messages:send; agent reads a contact and sends them a message. Fine. Agent reads a contact, exports the list, and sends messages to everyone. Also fine by the scopes as written — probably not fine by user intent. You'll see this first as customer complaints, not security alerts, because nothing was technically unauthorized.
Token exchange latency in loops. Well-designed delegation adds a token exchange call before every tool invocation. In a single-turn conversation that's a few dozen milliseconds; in a loop with parallel tool calls it compounds. Teams cache exchanged tokens to fix latency and lose the per-call audit trail. The right fix is exchange-in-parallel and short cache windows keyed on the specific action, not blanket caching.
Refresh token exposure. OAuth refresh tokens live in the agent runtime. If that runtime is compromised — a supply-chain attack on an agent framework, a leaked env var, a misconfigured log destination — the attacker can act as every user the agent is authorized for. You'll see this first as anomalous tool-call patterns, not as authentication failures, because the attacker has valid tokens. Sender-constrained tokens (DPoP or mTLS) meaningfully reduce this blast radius.
Consent decay. Users grant consent and forget. Six months later the agent still has valid refresh tokens for people who no longer use it. Every agent authorization system needs a consent expiry policy and a user-facing dashboard showing what's granted. Most don't.
Delegation without delegation semantics. The tool call succeeds, the audit log records the user's identity, but there's no record that an agent was in the chain. When someone asks "did the user do this or did their agent?", the log can't tell you. This is the failure mode we see most often and it's the one that turns a security incident into a legal incident.
We've been writing about the tools layer because that's where these authorization decisions actually get enforced — not in the model, not in the orchestrator, but in the runtime that sits between the two. Pontil's a Tools-as-a-Service platform: we make SaaS products accessible to AI agents, and the authorization boundary is one of the reasons that layer needs to exist as its own thing.
The Pontil runtime executes tool calls as the authenticated user, not as a shared service account. Token exchange, scope narrowing, and delegation chain preservation happen at the boundary — so the audit record for every tool call shows both the user whose intent triggered it and the agent that acted on their behalf. Capability scopes stay legible to the user at consent time; action scopes get minted per call at runtime. If you're stuck between "OAuth is enough" and "we need a full delegation architecture," the tools layer is where that fight gets resolved. Our writeup on why agent projects stall covers the pattern in more depth.
The honest answer is: mostly the same. Better models don't change the fact that a probabilistic caller acting on a human's behalf needs a boundary that enforces what the human is allowed to do. If anything, more capable models make the authorization boundary more important, because the space of actions the agent might attempt gets larger.
What will change is the shape of the consent surface. Right now consent is a one-time OAuth screen with a list of scopes. In two years it'll be something closer to a running budget — the user grants an agent an envelope of actions, the runtime tracks what's been used, and the user gets asked again when the envelope is exhausted or when the agent tries something outside its pattern. The infrastructure for that isn't quite here yet. The teams building agent projects now who are going to be in the best position when it does arrive are the ones who got the boundary right the first time — delegated identity at every tool call, scopes narrow enough to matter, delegation chains preserved in the audit record. Everything else is going to get rewritten. That part will hold.
Stay up to date on the ever changing agentic landscape.