What is per-call authorization for AI agents, and why does it beat per-session?

Per-call authorization means the agent gets a fresh, least-privilege, short-lived token for each individual tool call, scoped to one resource and one action, rather than carrying a single long-lived credential for the whole session. A per-session setup works the other way: it hands out a broad credential at session start and reuses it for every call that follows.

The gap between the two shows up the moment something goes wrong. An agent is a program that reads untrusted text and then decides what to do with its permissions. Plant instructions in that text, say a poisoned GitHub issue, a lead form, or a support ticket, and a per-session agent already holds broad standing authority that it will point wherever the injection tells it to. A per-call agent holds nothing broad to begin with. To cause real damage it would need a token scoped for that damage, and no such token was ever minted.

The practical result is a much smaller blast radius. Instead of everything the user can touch, the exposure is one call, one resource, for the next few seconds. You also gain a clean place to enforce policy, revoke access mid-session, and log activity. This is least-privilege applied at the granularity of the action rather than the login, and it is the non-human identity idea made concrete: the agent is its own principal with its own scoped credentials instead of a process quietly borrowing a human's broad session.

How per-session and per-call actually differ, mechanically

Per-session is the default most agent frameworks ship with, because it is the easy path. Authenticate once, stash a token, attach it to every outbound request. It is the same instinct that gave us long-lived API keys, and it fails for the same reason: the credential's power has no relationship to what any given call actually needs.

Per-call changes the flow. Before each tool call, the agent's runtime trades its identity for a narrow token that describes exactly this call: this resource, this action, this audience, expiring soon. A read of invoices/2024 gets a token good for reading invoices/2024 and nothing else. A different call gets a different token, freshly minted and freshly authorized.

What this buys you is non-transferability. With a per-session credential, the authority for call A is byte-for-byte identical to the authority for call B, so an attacker who redirects the agent can reuse A's credential for B at no cost. With per-call tokens, A's token is useless for B. The credential also becomes far less worth stealing, because it expires quickly and opens exactly one door. Steal it and you have won a few seconds of read access to a single invoice.

The obvious objection is cost, and it deserves a straight answer. Minting a token before every call means an extra hop to the authorization server, some issuance latency, and load on that server you did not carry when one cached session credential covered everything. Real, but tractable. RFC 8693 token exchange tokens carry their own expiry, so you cache a narrow token for its short TTL and let a burst of reads against invoices/2024 reuse it instead of paying the mint cost on every call. You tune the window: long enough to amortize the exchange, short enough that a leak dies fast. Per-call is about scope, not a religious one-round-trip-per-call rule.

One caveat to keep honest. A per-call token is still a bearer token, and a bearer token is worth exactly its possession. Grab it inside that short window and you can replay it, because holding it is the whole proof. Short expiry shrinks the window, it does not close it. To close it, bind the token to its sender so a stolen copy is inert in anyone else's hands. That is what DPoP (RFC 9449) does with a client-held key and what mTLS-bound tokens (RFC 8705) do with the client certificate. Pair short-lived per-call scope with sender constraint and a token lifted mid-flight is dead on arrival.

What the MCP authorization spec requires (revision 2025-06-18)

The Model Context Protocol authorization spec, revision 2025-06-18, never uses the phrase per-call authorization. It bakes the primitives in as hard requirements instead. The ones that matter here:

  • Authorization is per request, not per session. The Authorization header must ride on every HTTP request from client to server, even when those requests are part of the same logical session. There is no log in once and coast mode.
  • Audience validation is mandatory. An MCP server acting as an OAuth 2.1 resource server must verify that the access token was issued specifically for it, and must reject tokens that do not name it in the audience. A token minted for service X is dead on arrival at service Y.
  • No token passthrough. If an MCP server calls an upstream API, it must obtain a separate token from the upstream authorization server and must not forward the token it received from the client. That single rule closes the confused-deputy problem, where a downstream API wrongly trusts a forwarded credential.
  • Clients must send resource indicators. MCP clients implement RFC 8707, passing a resource parameter so tokens are audience-bound and cannot be replayed across services. One precise caveat: the spec makes the client-to-authorization-server binding conditional, it applies when the authorization server supports the capability, whereas the server-side audience validation above is unconditional. So a server always rejects a token that does not name it, even where a given authorization server cannot yet bind the audience at issue time.
  • The spec also calls for short-lived tokens and for rotating refresh tokens issued to public clients, to limit the fallout from any single leak.

Put those together and you have described per-call, resource-scoped, audience-bound authorization without ever naming the pattern. If you are building on MCP, the companion piece on MCP server security picks up where this leaves off.

The standards under the hood: RFC 8693 and RFC 8707

Two IETF documents supply the machinery, and neither is new or exotic.

RFC 8693, OAuth 2.0 Token Exchange, a Proposed Standard from January 2020, defines a Security Token Service: you hand in one token and get back another. Its important contribution is that it formally separates delegation from impersonation. Under delegation, principal A keeps its own identity while acting on behalf of B, expressed with the act (actor) claim and an optional may_act claim. Under impersonation, A simply becomes B and the trail is lost. Delegation is the one you want, because it preserves the whole Human to Agent to Service chain inside the token. Token exchange is the primitive that lets an agent obtain a fresh, least-privilege, audience-scoped token for each downstream call without breaking that chain.

RFC 8707, Resource Indicators for OAuth 2.0, published the same year, defines the resource request parameter that binds an issued token to a specific target audience. It is the mechanism MCP relies on so that each per-call token works at its intended server and nowhere else.

A caveat is worth stating plainly, since a vendor may eventually pitch this to you. Per-call authorization is the name of a design pattern, not a normative term in any single spec. No RFC carries that title, and no RFC mandates the pattern by name. What the standards give you are the parts, token exchange and resource indicators and per-request audience validation, which you assemble into the pattern yourself. It is also worth being clear that RFC 8693 and RFC 8707 are Proposed Standards offering building blocks, not a finished, interoperable per-call profile. If someone tells you a standard requires per-call by name, they are overselling it. The standards give you the pieces, not the assembled pattern.

Why prompt injection makes external authorization non-negotiable

The root cause is uncomfortable. LLMs mix trusted instructions and untrusted data in the same context window, and they cannot reliably tell which is which. Simon Willison's long-running analogy is SQL injection: the model concatenates your instructions with the attacker's text and runs the blend. This is not a bug you patch once. It is a structural property of how these models read input.

That is exactly why authorization has to live outside the model. You cannot prompt-harden or fine-tune your way to a model that reliably refuses injected instructions, just as you cannot comment your way to SQL that ignores malicious input. The enforcement point has to be a component the model cannot talk around, and it has to be checked at every call.

The standards bodies see the same shape. In the OWASP Top 10 for LLM Applications 2025, published 18 November 2024, Prompt Injection is LLM01, the number one risk for the second edition running. Right next to it sits LLM06 Excessive Agency, the category for agents handed too much autonomy and too many permissions. OWASP's own mitigation for Excessive Agency is least-privilege plus human authorization on sensitive actions. Per-call authorization is a way to operationalize both at once.

Worth keeping in perspective: this is an engineering threat model for a single control, not a complete security program. It is one strong beam, not the whole house.

Real incidents: what over-permissioned agents actually do

Two well-documented cases show the failure mode clearly. Both are responsibly disclosed proofs-of-concept rather than breaches observed in the wild, which is worth being precise about when you discuss risk internally.

The first is the GitHub MCP private-repo exfiltration, disclosed by Invariant Labs on 26 May 2025. A malicious public GitHub issue carried an indirect prompt injection. A user's agent, running the official GitHub MCP server, read the issue, followed the planted instructions, pulled data from the user's private repositories, and leaked it through an auto-created public pull request. Invariant's reading is that no server code bug was involved and that the cause was architectural: the agent held broad cross-repo session authority, so one poisoned input could steer it anywhere that authority reached. Their recommended fix was to restrict the agent to one repository per session and issue least-privilege tokens, which is per-call scoping under a different name.

The second is ForcedLeak in Salesforce Agentforce (Einstein AI), disclosed by Noma Security at CVSS 9.4. According to Noma, the issue was discovered on 28 July 2025, remediated by Salesforce on 8 September 2025, and publicly disclosed on 25 September 2025. Noma's account describes attackers stuffing instructions into a Web-to-Lead description field, which gave them a large block of text to work with. When an employee later queried lead data, Agentforce ran the injected instructions and exfiltrated data to an expired domain that was still on the CSP allowlist, a domain the researchers say they acquired cheaply. As in the GitHub case, the agent acted on broad standing permissions instead of an authority scoped to the task at hand. These figures come from a single vendor's disclosure, so treat them as Noma's reported account rather than independently confirmed detail.

Both cases rhyme with Willison's lethal trifecta, described on 16 June 2025: access to private data, exposure to untrusted content, and the ability to communicate externally. Line up all three and one poisoned input can exfiltrate data with no traditional vulnerability in the picture. Per-call authorization attacks the trifecta by making sure the agent never holds enough standing authority to complete the chain.

Per-call authorization best practices: how to prevent agent overreach

To put this into practice, here is the checklist that the specs and the incident responders actually point to.

  • Mint a fresh token per tool call via RFC 8693 token exchange, scoped to that call's resource and action, with the shortest expiry you can tolerate. One call, one token, one purpose.
  • Bind every token to its audience with RFC 8707 resource indicators, and have each server validate the audience claim and refuse anything not addressed to it. A stolen token should open exactly one door.
  • Sender-constrain the token so it is not pure bearer. Short expiry limits a leak, but DPoP (RFC 9449) or mTLS (RFC 8705) makes a token grabbed inside its window useless without the client's key. Short-lived and sender-bound together is what actually neutralizes token theft.
  • Never pass tokens through. When your agent's server calls an upstream API, exchange for a new upstream-scoped token rather than forwarding the client's. That is the confused-deputy fix, and it is a hard requirement in MCP 2025-06-18.
  • Use PKCE and exact redirect-URI matching to block code interception during the auth flow.
  • Scope the runtime, not just the token. Invariant's one-repository-per-session guidance generalizes: give each session the minimum surface it needs and no adjacent resources.
  • Grant access just in time. As agents move from reading data to acting on it, scope access per action rather than through a persistent session. Red Hat's analysis of MCP security makes the same case for least-privilege enforcement at the point of each tool invocation.
  • Rotate refresh tokens and keep access tokens short-lived so any single leak has a short, narrow window.
  • Keep untrusted input away from consequential actions. If a call can move money, delete data, or publish externally, gate it behind explicit human authorization and do not let injected text reach it unmediated. Do not assemble the lethal trifecta in the first place.
  • Assume the model is compromised and place the enforcement point somewhere it cannot argue with. The broader OWASP MCP Top 10 catalogs the MCP-specific risks these checks are meant to contain.

Govern the data plane, not only the actions

Per-call authorization governs what an agent is allowed to do. There is a second question that often gets skipped: what an agent can reach at all. Both matter, and the strongest posture answers them together.

That is the angle we build DataShield around. Tokenize sensitive fields at ingest, so that a hijacked agent which does slip its leash finds tokens where the PII used to be rather than raw data it can exfiltrate. Authorize each tool call on its own, with the ability to revoke authority mid-session the instant something looks wrong, instead of waiting for a long-lived session to time out. And seal every call into a tamper-evident audit chain you can verify afterward, so that the question of which call touched what, and under whose authority, has a cryptographic answer rather than a shrug.

The underlying point is this. Do not rely only on constraining what the agent will do based on its reasoning, because prompt injection can corrupt that reasoning directly. Constrain what it can reach and what each call is authorized to touch, outside the model, at the granularity of the individual call. A per-session credential cannot give you that granularity. Per-call can, and the building blocks for it, token exchange and resource indicators, have been available in the RFC series for years.

Three short watches on agent identity, MCP, and the injection that makes authorization matter.

Identity for AI Agents video

Identity for AI agents (Auth0, AI Engineer)

Model Context Protocol (MCP), clearly explained video

MCP, clearly explained (Greg Isenberg)

Prompt Injection, explained video

Prompt injection, explained (Simon Willison)

Frequently asked questions

What is per-call authorization for AI agents?

It is a design pattern where an agent obtains a fresh, least-privilege, short-lived token for each individual tool call, scoped to that specific resource and action, instead of reusing one broad credential all session. It is assembled from OAuth 2.0 Token Exchange (RFC 8693, 2020) and Resource Indicators (RFC 8707, 2020), and MCP's 2025-06-18 authorization spec mandates the underlying pieces: per-request authorization, audience validation, and no token passthrough.

Why does per-call authorization beat per-session authorization?

Because it makes authority non-transferable across calls. A per-session agent holds one broad credential, so an attacker who hijacks it via prompt injection can reuse that credential for any call the agent can make. A per-call agent only ever holds a token scoped to the one call in progress, expiring in seconds, so a redirected agent cannot recycle it into a different, more damaging action. The blast radius drops from everything the user can touch to one resource for a few seconds.

Does the MCP specification require per-call authorization?

Not under that name. The MCP authorization spec (revision 2025-06-18) requires the primitives that make it up: the Authorization header must be sent on every request even within one logical session, servers must validate token audience per RFC 8707, servers must not pass the client's token through to upstream APIs, and clients must use resource indicators. No spec is titled per-call authorization. The standards supply the parts you assemble into the pattern.

Do short-lived per-call tokens stop a stolen token?

Only partly. A per-call token is still a bearer token, so anyone who grabs it inside its short window can replay it, because possession is the whole proof. Short expiry shrinks that window but does not close it. To close it, sender-constrain the token with DPoP (RFC 9449) or mTLS-bound tokens (RFC 8705) so a stolen copy is useless without the client's key. Short-lived plus sender-bound is the combination that neutralizes token theft.

Can prompt injection be fixed by better prompts or model training instead?

No, and that is the crux. LLMs mix trusted instructions and untrusted data in one context and cannot reliably tell them apart, which makes prompt injection structural rather than a patchable bug. In OWASP's LLM01:2025 ranking it is the number one LLM risk. Authorization has to be enforced outside the model, at each call, by a component the model cannot talk its way past. Prompt hardening lowers frequency but never makes refusal reliable.

Have per-session agents actually been exploited in the wild?

The best-documented cases, the GitHub MCP private-repo exfiltration (Invariant Labs, 26 May 2025) and ForcedLeak in Salesforce Agentforce (Noma Security, CVSS 9.4, disclosed 25 September 2025), are responsibly disclosed proofs-of-concept rather than confirmed in-the-wild breaches. Both worked because the agent held broad standing session permissions that indirect prompt injection could redirect. The lesson holds regardless of whether anyone weaponized them: over-permissioned agents fail exactly this way.