pickuma.
AI & Dev Tools

Authenticating AI Agents: API Keys vs OAuth Device Flow vs Scoped Tokens

A practical breakdown of the three credential models for non-human callers — static API keys, the OAuth 2.0 device authorization grant, and short-lived scoped tokens — and when each one actually fits.

7 min read

Your agent needs to hit the GitHub API, your internal deploy service, and a customer’s Postgres. Nobody is at a keyboard. Whatever credential you hand it has to keep working through a 3am retry, survive a rotation, and leave a trail that tells you which run did what.

The three options you actually choose between — a static API key, the OAuth 2.0 device authorization grant (RFC 8628), and short-lived scoped tokens — are not three flavors of the same idea. Each answers a different question about who is present when the credential is minted and who is accountable when it is used. Picking the wrong one does not fail loudly on day one. It fails on the day you need to revoke something, and discover the credential is copy-pasted into four CI configs and a developer’s shell profile.

What each mechanism actually assumes

A static API key assumes the secret is the identity. There is no user, no session, no expiry. Whoever holds the string is the caller. That is genuinely fine for a narrow class of cases: a single-tenant background job, an internal service where the blast radius is already bounded, a local dev loop. It is not fine the moment the key can act on behalf of more than one principal, because the token carries no answer to “on whose behalf?” Your audit log records the key, and the key is the same for every run.

The operational tax is rotation. A static key has no natural refresh point, so rotating it means finding every place it was copied to. Agents make this worse than normal service-to-service calls, because agent frameworks encourage stuffing credentials into environment files, MCP server configs, and tool definitions that get shared across machines.

The OAuth 2.0 device authorization grant assumes a human is reachable, just not on this device. That is the whole point of RFC 8628: the client is input-constrained (a TV, a CLI, a headless box), so it displays a code, the human opens a browser somewhere else, approves, and the device polls the token endpoint until it gets an access token and refresh token.

The assumption that matters is reachable human. Device flow is not a machine-to-machine grant. It is a delegation grant with a deferred consent screen. If your agent runs on a schedule with nobody watching, device flow only works because a human ran it once, weeks ago, and the refresh token has been quietly rolling over ever since. That is a legitimate design — it is roughly how CLI tools like gh auth login behave — but be clear about what you built: an agent that inherits a specific person’s authority, indefinitely, with that person’s name on every action in the audit log.

Short-lived scoped tokens assume the agent has its own identity, and that the identity is separable from the credential. The credential is minted on demand, narrow in scope, and expires in minutes. In OAuth terms this is the client credentials grant (RFC 6749 §4.4) when the agent acts as itself, or token exchange (RFC 8693) when it needs to act on behalf of a user for one specific call. In cloud terms it is workload identity federation — the agent proves what it is via a platform-issued attestation and trades that for a scoped access token. SPIFFE/SPIRE is the vendor-neutral version of the same shape.

This is the model that fits non-human callers, because it is the only one where “the agent” and “the agent’s current credential” are different objects. You can revoke one without hunting for the other.

Pick by who is present, then by what can be scoped

Run the decision in two passes.

First pass — who is present at authorization time?

  • A human, at the moment of the call. Use a normal authorization code flow with PKCE in the surrounding app and pass a per-request token down to the agent. Do not promote it to a stored credential.
  • A human once, then never again. Device flow, with an explicit refresh-token lifetime and a re-consent interval. Write down what happens when that person leaves.
  • Nobody, ever. The agent is a workload. Give it a workload identity and mint scoped tokens per task. Client credentials or federation, not a key file.

Second pass — can the authority be narrowed to the task?

This is where most implementations stop early, and it is the part that actually limits damage. A token scoped to repo is not scoped. A token scoped to one repository, write access to one branch, valid for eight minutes, is scoped. GitHub App installation tokens are a good reference implementation: they expire in an hour and can be restricted to specific repositories and permissions at request time. Cloud STS tokens support similar narrowing through session policies and duration limits.

For agents specifically, add two properties that human-facing OAuth rarely needs:

  1. Per-run distinctness. Each agent run should be traceable to its own token, so a compromised or misbehaving run is bounded and identifiable. A single long-lived token shared across runs collapses your audit log into one row.
  2. Sender constraint. Bearer tokens are replayable by anyone who obtains them, and agents leak them through logs, traces, and prompt context more readily than normal services do. DPoP (RFC 9449) or mTLS-bound tokens (RFC 8705) bind the token to a key the holder must prove possession of, so a stolen token alone is not enough.

The Model Context Protocol authorization spec pushed the ecosystem toward this shape — MCP servers act as OAuth resource servers, and clients are expected to obtain tokens with an explicit audience rather than accept a shared secret. If you are building agent tooling now, matching that pattern costs you little and keeps you compatible with where the tooling is heading.

OpenCode

An open-source terminal coding agent. Useful reference material if you are designing agent auth: its provider and MCP credential handling is readable, so you can see exactly where tokens are stored, scoped, and passed to tools rather than guessing at a closed implementation.

Open source; bring your own model API key

Try OpenCode

Affiliate link · We earn a commission at no cost to you.

What to do if you already shipped API keys

You probably did, because it is what every SDK quickstart hands you. The migration does not have to be a rewrite.

Start by putting a token broker between your agents and the static keys. The agent authenticates to the broker with a workload identity, the broker holds the upstream long-lived credential, and it issues a short-lived, narrowly scoped token per task. Nothing upstream changes. You get expiry, per-run attribution, and a single place to revoke — which is most of the benefit of the full model.

Then fix the audit gap. Add a run identifier that travels with every call the agent makes, and make sure it lands in the same place your upstream logs land. If you cannot answer “which agent run deleted this record?” from logs alone, the credential design is not finished, regardless of which grant type you chose.

Last, set an expiry on anything that currently has none. A key that never expires is a key nobody ever tests the rotation path for, and rotation paths that have never been exercised do not work when you need them at 3am.

FAQ

Can I just use the OAuth client credentials grant for everything?
For agents that act as themselves, yes — that is the intended use. It stops working when the agent needs to act on behalf of a specific user, because client credentials carry no user context. For that case, use token exchange (RFC 8693) to trade a user token for a downstream token with a narrower audience, so the delegation chain stays visible.
Is the device flow insecure for agents?
The grant itself is fine and it is the right answer for input-constrained clients. The risk is what you do with the refresh token afterward. An unattended agent holding an indefinitely renewable refresh token is a standing grant of a human's authority with no review point. Set a refresh-token lifetime, require periodic re-consent, and tie the grant to an account lifecycle event.
Do short-lived tokens break long-running agent tasks?
Only if the agent fetches a token once at startup. Fetch per call or per tool invocation from a local credential provider that handles refresh, the same way cloud SDKs already do for instance credentials. A task running for hours should still be presenting minute-scale tokens.

Tools used in this review

Some links above are affiliate links. We may earn a commission if you sign up. See our disclosure for details.

Related reading

See all AI & Dev Tools articles →

Get the best tools, weekly

One email every Friday. No spam, unsubscribe anytime.