OAuth tokens for AI agents: device flow and rotating refresh tokens

API keys in AI agent context windows are a known problem — writeups and tools for it appear every week. OAuth tokens are the nastier cousin nobody talks about, because they’re worse in three ways:

  1. They’re long-lived by design. A refresh token grants access for days, months, or until revoked. An agent that leaks a refresh token leaks standing access, not a single request.

  2. They rotate. Access tokens expire and get refreshed on a schedule, so any naive “put the token in a file” approach breaks hourly — which pushes people toward even worse practices (auto-refresh scripts writing tokens to disk, where the agent can read them).

  3. They’re scoped to real accounts. A leaked Google or Lark token is a direct path into someone’s mail, calendar, or drive — not a sandboxed API key.

If your agent calls Google or Lark APIs, you need a way to give it access without ever putting a token in its context window.

The device flow: approval without the agent

The login flow that fits agents is the RFC 8628 device authorization flow — the “enter this code on another device” pattern you’ve seen on smart TVs:

$ trustless oauth login google api/google
https://oauth2.googleapis.com/device/code?user_code=ABCD-1234   # open this in a browser
{"key":"api/google","provider":"google","expires_at":"2026-08-13T12:00:00Z"}

The user opens the URL, approves in their own browser, and the tokens are stored by the broker as a compact single-line JSON entry in the credential store (type=oauth). The agent never sees the approval URL, never sees the tokens, and never sees a token value in any output.

Refresh without exposure

The part that usually goes wrong is the refresh cycle. Access tokens expire in an hour; something has to fetch a new one. If that “something” is the agent, the agent holds the refresh token — the standing-access credential — and everything about the problem gets worse.

In the broker design, refresh happens at the credential layer:

  • trustless run -s <key> and trustless proxy resolve the entry to a fresh access token at use time, refreshing automatically on expiry.
  • trustless oauth refresh <key> forces a refresh without waiting — and the access token value is never printed to any output the agent could capture.
  • trustless oauth status <key> reports valid / expired / reauth_required, so automation can detect a revoked refresh token (invalid_grant) and ask the user to re-login instead of silently failing.

Refresh tokens are treated as single-use: rotated on every refresh, so a token that leaks after a refresh is already dead. Combined with the broker holding the current token, a credential that leaks from any layer has a short half-life and zero standing value.

Providers are config, not code

Google and Lark ship as built-in provider definitions (token endpoints, device endpoints), so the only setup is registering the app in the provider’s developer console and filling in client_id / client_secret and scopes:

[oauth.providers.google]
client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
scopes = ["https://www.googleapis.com/auth/gmail.readonly"]

Same shape as every other credential in the store, resolved the same way, audited the same way.

The principle

Agents are untrusted callers. The moment a credential can outlive the call that uses it, or can be carried into a context window, it’s a liability. OAuth is where this bites hardest, because refresh tokens are exactly that: credentials that outlive everything.

This is built into trustless, a zero-dependency credential broker CLI for AI agents (Go, single binary, 321 tests with -race, cosign-signed releases with SBOM). OAuth entries live alongside pass/Bitwarden secrets, resolve to fresh access tokens at use time, and every resolution lands in an append-only audit log.

How are you handling OAuth for your agents today — token in a file, or a refresh script the agent can read?