OAuth has been the dominant way third-party apps get permission to act on a user's behalf for fifteen years. It works. It is good for what it was designed for: stable third-party apps that ask for a bundle of permissions once, get a token, and use it for months.
Agents break almost all of those assumptions. They are not stable (the model behind them changes weekly). They don't ask once (they want different scopes for different tasks). The bundle of permissions is too coarse (read:all is the wrong granularity for a single-task agent). And revocation is binary (revoke the token, lose all access) when what you want is per-workspace, per-tool, per-time-window control.
This piece is on the shape of the gap. Not "OAuth is bad" — OAuth is fine for what it does. The point is that OAuth scopes don't carry the granularity agents need, and the scope abstraction has to shift.
What OAuth scopes were designed for
A canonical OAuth flow:
- A third-party app (Asana, say) asks for permission to read and write your Google Calendar.
- The user clicks "Allow." The app gets a token with scopes
calendar.read calendar.write. - The app uses the token for as long as it wants, until the user revokes it from the Google account settings.
Three properties of this flow:
- Coarse-grained scope. "Read all calendar" or "write all calendar" — there is no per-calendar, per-event scope.
- Long-lived token. The token is good for months, sometimes years.
- Identity binding to the integration. The token is "Asana acts on behalf of this user." Specific instances of usage aren't distinguished.
For Asana, this is fine. Asana is a stable, audited, well-understood third-party app. Coarse scope is acceptable because the user trusts Asana broadly. Long-lived tokens are fine because Asana isn't changing what it does week to week.
What agents need
Now the same model applied to an agent:
- An agent (Argus) asks for permission to read and write your Google Calendar.
- The user clicks "Allow." Argus gets a token with
calendar.read calendar.write. - Argus uses the token for the next year, across thousands of operations, on every calendar in your account, with no further confirmation.
This is wrong in three ways. Argus is not a stable third-party app — it's an agent whose behavior depends on its prompt and the model it's running. Scope calendar.write is way too broad — Argus probably needs to write to one calendar for one task. And the token is a year long when the task is a week.
The shape of what agents need:
- Fine-grained scope. Per-resource (this calendar, not all calendars), per-action (this event, not all events), parameter-bound.
- Short-lived authority. Hours or days, not months.
- Per-task or per-workspace binding. The agent has authority for this task in this workspace, not org-wide forever.
- Replayable consent. When the agent's task changes, the user re-confirms. The cost of re-confirmation is low.
Notice that almost every property is a tightening. The OAuth scope is an upper bound; agents want to operate well below it. The system needs to express that distinction, not just rely on the agent to behave.
What's broken with bolting agent support onto OAuth
We've seen three patterns of teams trying to use OAuth scopes for agents:
1. Mint a fresh OAuth token per task. The user re-grants access every time the agent gets a new task. The mechanics work but the UX is brutal — the user clicks "Allow" twenty times a day. Users stop reading the consent screen, which defeats the security purpose of consent.
2. Mint one OAuth token, narrow scope at the application layer. The agent gets a broad OAuth token but the application layer enforces narrower scope ("Argus can only write to this calendar"). This works in the happy path but the broad token still exists — if the application's scope-narrowing has a bug, the agent has full access. The audit trail also shows broad scope, not the narrow scope the agent actually used.
3. Use OAuth tokens with rotation + per-call confirmation. Some teams add a confirmation flow on top of OAuth (the consent gate pattern) for dangerous operations. This works for the dangerous ops but doesn't solve the underlying scope-too-broad problem for routine ops.
None of these are wrong, but they're all working around the OAuth model rather than replacing it. The replacement is a finer abstraction.
What replaces it: workspace-scoped grants
The substrate change: instead of granting OAuth scopes to an agent, grant the agent workspace memberships with roles.
Concretely:
- The user creates Argus.
- Argus has zero access by default.
- The user adds Argus as an editor in workspace A.
- The user adds Argus as a viewer in workspace B.
- Argus can write in A, read in B, and do nothing in C.
This is the model we covered in AI-agent-first primitives — agents have permissions via workspace memberships, not OAuth tokens.
Why this is better:
- Granularity matches the actual unit of work. A workspace is the surface where work happens. Granting access per workspace matches how teams think about scope.
- Revocation is per-workspace. Removing Argus from workspace B doesn't affect workspace A.
- Membership is auditable. The list of workspaces Argus is in is queryable. OAuth scopes attached to a token are usually opaque after the fact.
- Multi-org boundaries hold. Argus can be a member of workspaces in its org and not others, by construction.
What about external resources?
A real product needs to grant agents access to things that aren't workspaces — Google Calendar, Slack channels, GitHub repos. OAuth still has a role here, but it should be the user's OAuth grant, not the agent's. The agent uses the user's OAuth token only for resources it has been explicitly granted access to in the workspace context.
Concretely: the user grants their workspace access to their Google Calendar (the workspace gets the token). Argus, as a member of the workspace, can use the workspace's Calendar access for things scoped to the workspace's purpose. Argus cannot use the OAuth token to read the user's other calendars — the workspace boundary holds.
This is sometimes called delegated authority via the workspace. The workspace is the principal that holds the OAuth grant; the agent borrows it within the workspace's scope. It's slightly more work to implement than handing the agent a token directly, but it preserves the workspace-scoped boundary.
Time-bounded grants
A third dimension: agents often need temporary authority. Argus is helping with the launch this week; Argus needs broad-ish access this week; Argus does not need broad-ish access next month.
The pattern: grants have an expires_at. Argus's editor role in workspace A can be granted for 7 days. After 7 days, Argus is automatically demoted to viewer (or removed entirely). The user re-grants if the work continues.
The cost is one re-confirmation per period. The benefit is that authority doesn't accumulate indefinitely. If Argus stops being useful, its access expires on its own.
This is hard to express in OAuth scopes (which don't typically support expiration beyond token lifetime). It's natural in workspace-membership grants.
A composite picture
Putting it together, the agent's authority is the union of:
- Its workspace memberships (which workspaces it can act in).
- The roles in each (viewer / editor / admin).
- The time bound on each grant (when does it expire).
- The consent gates on dangerous operations (which actions need a fresh handshake).
- The OAuth grants on resources the workspace owns (what external surfaces the agent can touch).
OAuth is one component, not the whole story. Treating OAuth scopes as the agent's permission model is the source of the gap. Treating workspace memberships as the model, with OAuth as a lower-level resource grant, is the shape that scales.
What to build
If you're starting from a system that uses OAuth scopes for agent permissions, the migration:
- Add workspace memberships to your data model if you don't have them. Memberships have user_id, workspace_id, role.
- Move agent permissions from token scopes to workspace memberships. The agent's authority is "what workspaces is it a member of, with what role?"
- Add
expires_atto memberships. Time-bounded by default for agents (open-ended for humans). - Wrap external OAuth grants so they're workspace-owned, not agent-owned. The agent borrows them only within the workspace.
- Layer consent gates on top for dangerous operations.
The result is a permission model where OAuth still does its job (granting access to external resources) but the agent's authority is expressed in primitives that match what agents actually need: scoped, time-bounded, per-workspace, revokable.
For more on the broader contract for dangerous operations, see The dangerous-ops contract. For consent gates as the layer above scope, see Consent gates for dangerous operations.
FAQ
What is an OAuth scope?
A capability granted to an OAuth token at issuance, like calendar.read or repo.write. The scope describes what API endpoints the token can call. Scopes are typically coarse (per-API, sometimes per-action), long-lived (months to years), and granted once at user consent.
Why don't OAuth scopes work well for agents?
Three reasons. They're too coarse (per-API, not per-resource). They're too long-lived (agents need short bursts of authority). And they bind to the integration, not to per-task or per-workspace context. Agents need permissions that match the actual unit of work, which is the workspace, not the API.
Should I stop using OAuth?
No. OAuth is still the right way to grant access to external resources (Google Calendar, GitHub, Slack). The change is who holds the OAuth grant — let the user grant access to a workspace, and let the agent borrow that access only within the workspace's scope. The agent doesn't hold OAuth grants directly.
What's a workspace-scoped grant?
A permission expressed as "this principal is a member of this workspace with this role." The principal can be a human or an agent. The role determines what they can do in the workspace. Adding/removing the membership is the unit of permission change. This replaces "agent has OAuth scope" as the agent's permission model.
How do time-bounded grants work?
Each membership has an expires_at timestamp. Once it passes, the membership is gone — the agent no longer has access. The user re-grants if the work continues. The expiration is per-membership, so different workspaces can have different expirations. Time-bounding prevents authority from accumulating indefinitely.
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "OAuth scopes for agents: what's broken",
"description": "OAuth scopes were designed for stable third-party integrations. Agents need scopes that are time-bounded, parameter-bound, and revokable per workspace. The shape of the gap, and what to build instead.",
"datePublished": "2026-04-26",
"author": { "@type": "Person", "name": "Flint" },
"publisher": { "@type": "Organization", "name": "Dock", "url": "https://trydock.ai" },
"image": "https://trydock.ai/blog-mockups/style-d-dreamscape/oauth-scopes-for-agents.webp",
"mainEntityOfPage": "https://trydock.ai/blog/oauth-scopes-for-agents"
}