Skip to main content
Guides ••13 min read•

OAuth2 for AI Agents Built to Survive a 40-Step Run

A hands-on OAuth2 for AI agents guide: own client identity, on-behalf-of token exchange, per-tool scopes, and revocation that stops a run mid-step.

OAuth2 for AI agents issues each autonomous workload its own client identity so every step of a long run stays scoped, attributable, and revocable.

Somewhere in production right now there is an AI agent holding a refresh token that belongs to a staff accountant. It received the token during onboarding, renews it silently, and every action it takes in the accounting system, the CRM, and the data warehouse lands in the audit log under her name. Suspend her account and, on most providers, the agent keeps working: the outstanding access token still validates, and the refresh cycle only notices the suspension at its next attempt. That gap is where most autonomous workloads live today, and closing it is the actual job of OAuth2 for AI agents.

Stay in the loop.

Get the latest posts and exclusive content delivered to your inbox.

Join 13 readers. No spam. Unsubscribe in one click, anytime.

The fix is layered delegation, and every pattern below hangs off it. Give each agent its own OAuth2 client identity. On top of that identity, mint short-lived, narrow authority for each tool call through token exchange. A 40-step run can then be cut off at any step, and every action is attributed to both the authorizing user and the acting agent. Getting there takes five decisions: issue, delegate, scope, size, and revoke.

Why OAuth2 for AI Agents Starts With Machine Identity

A borrowed user token fails in two specific ways, and both deserve precise names.

The first is scope inheritance. The token carries whatever the user once granted, usually the union of everything they do personally. For a human that breadth is survivable because humans are slow. An agent is fast and literal, so the same breadth becomes an unbounded actuator, and prompt injection, a misread goal, or a stale instruction can convert it into actions at machine speed.

The second is misattribution. Downstream services log the token's sub claim, which is the person. Nothing in a standard log line distinguishes a click from an autonomous decision, so incident response fails in both directions: you cannot reconstruct what the agent did, and you cannot prove what the user did not do.

Both failures point at the same correction, and it comes straight from the OAuth2 framework. The client credentials grant exists to authenticate a workload rather than a person. Register the agent as a client and the token names the agent, every tool call becomes attributable, and revocation has a target that is not a human being. The tool ecosystem is converging here too, since the Model Context Protocol authorization spec builds on OAuth 2.1 and expects a connecting agent to arrive as an OAuth client. Machine identity for agents is the foundation everything else in this guide stands on.

Choosing Between Agent Identity and On-Behalf-Of Delegation

The agent identity vs user identity decision should be made per workload, not per company, and it is the first call in any AI agent authentication design. Two modes cover almost everything.

QuestionAgent acts as itselfAgent acts on behalf of a user
Does the tool need the user's own rights?No. Authority is granted to the client at registration.Yes. The action must resolve to the user, like their calendar.
Consent storyAn admin grants the client its scopes once.The user consents, then delegates per run.
Audit line readsagent-triage-bot-01 performed the actionuser 4821 authorized it, agent-triage-bot-01 performed it
Blast radius of a leaked tokenThe client's granted scopes, nothing wider.The user's authority, narrowed by the exchange scope.
FitsMonitoring, indexing, batch jobs, agent-to-agent calls.Inbox triage, scheduling, anything touching personal data.

Most real deployments are hybrid, and that is fine. The same agent authenticates as itself for plumbing, its vector store and its log sink, then switches to on-behalf-of delegation for user-data tools. Decide per tool, not per agent.

Pattern 1. Issue the Agent Its Own Client Credential

To issue OAuth2 client credentials to an AI agent, register it as a client on your identity provider and treat that registration as the agent's birth certificate. The client_id is what audits, grants, and revocation hang from.

Confidential or public client

If the agent runs where you control the runtime, register a confidential client. It holds a secret or private key and authenticates at the token endpoint. If parts run where you cannot keep a secret, say a browser extension or an edge function, treat them as public and lower expectations accordingly: anyone who can read the client can request tokens as it. The usual answer is a small confidential token-broker service that the untrusted parts call.

Where the secret lives

A secret in a checked-in environment variable is a breach on a delay. Keep credentials in a secret manager with tight access and scheduled rotation, or remove the static secret where your platform supports it. SPIFFE workload identity gives each workload a short-lived, verifiable identity that suits agents running inside your own infrastructure.

The grant itself is the client credentials grant from RFC 6749:

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
client_id=agent-triage-bot-01
client_secret=********
scope=ops.read incidents.write

The response carries an access token, its lifetime, the granted scopes, and by design no refresh token: RFC 6749 states that one SHOULD NOT be included for this grant, so on a compliant provider the confidential client re-authenticates at the token endpoint when the access token expires. That omission is also why the rotation trap waiting in Pattern 4 never applies here. The token now identifies the agent itself, so resource servers log client_id on every call. Auth0's client credentials flow documentation walks the same grant with concrete provider syntax.

Pattern 2. Delegate User Authority With Token Exchange (RFC 8693)

OAuth 2.0 token exchange lets an agent trade a user's token for a short-lived credential that names both the authorizing user and the acting agent.

Agent-as-itself cannot read Alice's calendar, and Alice's raw token should never be pasted into an agent's tool headers. On-behalf-of token exchange for AI agents resolves both at once, standardized as OAuth 2.0 Token Exchange in RFC 8693: the agent trades the user's token for a new one that names both parties.

The exchange request

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
client_id=agent-triage-bot-01
client_secret=********
subject_token=<the user's access token>
subject_token_type=urn:ietf:params:oauth:token-type:access_token
scope=calendar.read
audience=https://calendar.internal.example

The agent authenticates as itself, presents the user's token as the subject, and asks for only the scopes this one tool call needs.

What comes back, and who it names

Decoded, the issued token looks like this:

{
  "sub": "user-4821",
  "act": { "sub": "agent-triage-bot-01" },
  "scope": "calendar.read",
  "aud": "https://calendar.internal.example"
}

The user stays the subject while the agent is recorded under the actor claim, whenever your authorization server supports act. The downstream API logs both values, so an audit can distinguish who authorized an action from who performed it. That two-party record is the entire reason to exchange rather than forward. Microsoft's Entra on-behalf-of flow implements the same chain with vendor-specific parameters, useful if your stack already lives there.

Pattern 3. Scope Credentials Down Per Tool Call

The token exchange in Pattern 2 asked for calendar.read at a single audience, and no person chose those strings at call time. Nobody can: the LLM decides mid-run which tool to call next, so a tool-call token's scopes are unknowable at registration. Registration still sets the ceiling of what the client may request; what is missing is the per-call decision, which must be derived at runtime. Scoping agent permissions down per tool call therefore means deriving it from a mapping you wrote and reviewed in advance.

That mapping is a tool registry, and it is the agent-specific half of this pattern:

  • Tool and arguments to scopes. Each registry entry maps a tool and its argument patterns to the minimal scope set any invocation can justify: calendar.lookup gets calendar.read, calendar.reschedule gets calendar.read calendar.write. The registry owns the strings, and the model never sees or chooses a scope.
  • One audience per token, enforced. Each entry also names exactly one resource, and the registry refuses to mint a token that names two. That turns one-audience-per-token from a convention into code, which matters because scope names are just strings and read collides across every API you run. RFC 8707 resource indicators let the client request a token for exactly one resource, and each resource server should reject any token whose aud is not itself.
  • Review the strings where they live. Registry entries are configuration, so review them like code. Any scope set wider than the tool's arguments can justify gets cut, and a string that would fail review on a human-facing service should fail harder here, because the caller will use every scope it is handed, every call, at machine speed.

The registry is also your incident containment. A compromised or over-prompted agent token confined to one resource and one scope set turns a potential full-account compromise into a narrow single-tool incident, bounded by the token's short life.

Pattern 4. Size Token Lifetime for a 40-Step Loop

Token lifetime for long-running agent loops is a budget question, so do the arithmetic before accepting defaults. Assume 40 steps at a conservative 3 minutes each for retrieval, reasoning, and tool latency: the run lasts about two hours. A common 60-minute access token expires around step 20, mid-flight, between a plan and its completion.

Three workable answers, in rising order of discipline:

  1. Longer tokens push the problem around. A two-hour token survives the run but widens the revocation blind spot, and Pattern 5 pays for every extra minute.
  2. Refresh-before-call. Before each tool call, check remaining lifetime; below a floor such as 120 seconds, refresh first. The token is then always fresh enough for the next call, never for the whole run.
  3. A runway budget for destructive steps. Require headroom, say five minutes, plus revalidation before any irreversible mutation, so a run never begins an action it cannot finish.

One trap deserves its own paragraph. The OAuth 2.0 Security BCP recommends refresh token rotation with reuse detection, and reuse detection typically revokes the entire token family. If two parallel branches cached the pre-rotation refresh token, the loser's next refresh reads as token theft and the family dies, taking every branch down at once. This is where OAuth2 for AI agents diverges hardest from human-session habits: a best practice for people becomes a single point of failure for a fleet of branches. Long loops need one serialized refresher, one token cache, one writer.

Pattern 5. Revoke Authority Mid-Run

Token revocation shuts down an agent mid-run by killing its refresh tokens, revalidating before destructive steps, and halting the loop itself.

An access token's lifetime is the agent's revocation blind spot: within that window, a revoked actor can still act. Knowing how to revoke an agent token mid-run therefore means building a gate with three parts.

  1. Revoke at the source. Call your provider's token revocation endpoint against the refresh token and the exchanged tokens, and disable the client outright if the agent itself is compromised. Whether outstanding access tokens die immediately depends on the provider, so assume they do not.
  2. Revalidate before sensitive steps. Possession of a token proves nothing once it is revoked, so before destructive steps (send, delete, pay, deploy), query the token introspection endpoint defined in RFC 7662 rather than trusting the JWT. It costs a round trip; spend it on writes and skip it on reads.
  3. Stop the run itself. Revoking tokens blocks new tool calls, not the loop that wants to make them. Mark the run halted in your control plane and have the tool-call wrapper check that flag first, so a revoked run exits cleanly and every rejected call lands in the audit log with subject and actor attached.

Failure Modes to Design Against

FailureWhat you seeRoot causeFix
Expiry mid-step401 from the tool at step 23 while context is still validexp passed between the lifetime check and the callRefresh-before-call, plus one retry on 401
Rotation kills siblingsParallel branches fail together with invalid_grantConcurrent refreshes tripped reuse detection and revoked the familyOne serialized refresher and a shared cache
Audience confusionCalendar token accepted by the wiki APIaud never enforced, scope strings collideResource indicators plus aud checks on every resource server
The god tokenThe agent's credential granted every scope at registrationConvenience at issuance, nobody narrows laterLeast privilege at grant time, plus sender-constraining

On that last row: DPoP sender-constrained tokens from RFC 9449 bind each token to a key the agent holds, so a token that leaks into logs or tracing still fails replay before it expires. Cheap insurance for credentials that transit many systems.

Trade-Offs and Simpler Alternatives

OAuth2 for AI agents is worth its complexity only past a certain bar, and it is fair to say where that bar sits.

DecisionCheap optionCostly optionRule of thumb
Access token lifetimeLong, fewer refreshesShort, tighter revocationShorten as steps get more destructive
Token validationLocal JWT verificationIntrospection round tripLocal for reads, introspect before writes
Exchange cadenceCache the exchanged token until expExchange per tool callPer-call for anything user-scoped
Auth modelStatic API keyRegistered OAuth2 clientSee below

A static API key remains a defensible choice for a single-tool agent with no user context: keep it in a manager, rotate it on a schedule, done. OAuth2 earns its overhead once a second tool appears, a user's authority must flow through the agent, or you need to stop a run without redeploying. Even at the bottom of that range, a client credential still beats a shared key because it is individually revocable and individually attributable.

Implementation Checklist

Issuance

  • Every agent is a registered OAuth2 client with its own client_id
  • Secrets live in a manager or are replaced by workload identity, never in repos

Delegation and scoping

  • User-context tools go through token exchange, never forwarded raw user tokens
  • Every per-call token follows Patterns 2 and 3: sub and act both logged, one audience, registry-minimal scopes

Lifetimes

  • Pattern 4 applied in full: refresh-before-call floor, runway before destructive steps, one serialized refresher

Revocation and audit

  • The revocation path is tested end to end: refresh token, exchanged tokens, client disable
  • A run-level kill flag is checked by the tool-call wrapper before every call
  • The audit log can answer which agent, authorized by whom, did what, and when

Back to the accountant from the first paragraph. Under layered delegation, her suspension lands at the next tool call: the wrapper sees the halted flag, introspection reports the token dead, the loop exits, and the log names the agent. That is the difference between an agent you demo and one you let near production.

Stay in the loop.

Get the latest posts and exclusive content delivered to your inbox.

Join 13 readers. No spam. Unsubscribe in one click, anytime.

About the author

Tyler Brooks

Tools Analyst

Tyler has tested developer tooling for a decade, first as a platform engineer and now as an independent analyst. He reviews models, frameworks, and APIs the way he would want them reviewed before relying on them for real work.

Related Posts