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.

In this article
- 1.Why OAuth2 for AI Agents Starts With Machine Identity
- 2.Choosing Between Agent Identity and On-Behalf-Of Delegation
- 3.Pattern 1. Issue the Agent Its Own Client Credential
- 4.Confidential or public client
- 5.Where the secret lives
- 6.Pattern 2. Delegate User Authority With Token Exchange (RFC 8693)
- 7.The exchange request
- 8.What comes back, and who it names
- 9.Pattern 3. Scope Credentials Down Per Tool Call
- 10.Pattern 4. Size Token Lifetime for a 40-Step Loop
- 11.Pattern 5. Revoke Authority Mid-Run
- 12.Failure Modes to Design Against
- 13.Trade-Offs and Simpler Alternatives
- 14.Implementation Checklist
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.
| Question | Agent acts as itself | Agent 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 story | An admin grants the client its scopes once. | The user consents, then delegates per run. |
| Audit line reads | agent-triage-bot-01 performed the action | user 4821 authorized it, agent-triage-bot-01 performed it |
| Blast radius of a leaked token | The client's granted scopes, nothing wider. | The user's authority, narrowed by the exchange scope. |
| Fits | Monitoring, 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)

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.lookupgetscalendar.read,calendar.reschedulegetscalendar.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
readcollides 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 whoseaudis 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:
- 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.
- 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.
- 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

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.
- 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.
- 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.
- 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
| Failure | What you see | Root cause | Fix |
|---|---|---|---|
| Expiry mid-step | 401 from the tool at step 23 while context is still valid | exp passed between the lifetime check and the call | Refresh-before-call, plus one retry on 401 |
| Rotation kills siblings | Parallel branches fail together with invalid_grant | Concurrent refreshes tripped reuse detection and revoked the family | One serialized refresher and a shared cache |
| Audience confusion | Calendar token accepted by the wiki API | aud never enforced, scope strings collide | Resource indicators plus aud checks on every resource server |
| The god token | The agent's credential granted every scope at registration | Convenience at issuance, nobody narrows later | Least 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.
| Decision | Cheap option | Costly option | Rule of thumb |
|---|---|---|---|
| Access token lifetime | Long, fewer refreshes | Short, tighter revocation | Shorten as steps get more destructive |
| Token validation | Local JWT verification | Introspection round trip | Local for reads, introspect before writes |
| Exchange cadence | Cache the exchanged token until exp | Exchange per tool call | Per-call for anything user-scoped |
| Auth model | Static API key | Registered OAuth2 client | See 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:
subandactboth 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
AI Agents vs Workflows? Count Your Branches First
AI agents vs workflows: enumerate your branches, price each runtime decision point, and promote to an agent only when the branches can't be known ahead.
LLM Prompt Caching Can Cut Input Costs Up to 90%
LLM prompt caching can cut agent loop input costs up to 90%. Compare OpenAI, Anthropic, Gemini, and Bedrock on TTLs, breakpoints, and real savings math.
AI Training Preferences robots.txt, Set and Honor Them
AI training preferences robots.txt support is here. Set publisher policies and build crawlers that honor them, with configs and compliance steps.


