OAuth 2.0 explained: tokens, scopes, and flows in plain terms

OAuth 2.0 has a reputation for being impenetrable, but the core idea is simple: instead of giving an app your password, you give it a limited, expiring credential that says “this app may act on my behalf, for these specific things, until this time.” Everything else — tokens, scopes, flows — is machinery around that sentence.

Tokens: what they are and why they expire

An access token is a credential the client presents to an API. The API checks it, and if it is valid, serves the request. Two properties matter:

  • Limited. A token is tied to a scope, so an app that only needs to read your calendar cannot also send email.
  • Expiring. Tokens die after a short lifetime — minutes to hours — so a stolen token stops working quickly.

Access tokens are not passwords, but they behave like them: anyone who holds a valid token can use it. That is why short lifetimes and transport security matter more than what is printed inside the token.

Scopes: the permission model

Scopes are the strings that define what a token may do — calendar.read, mail.send. When an app requests authorization, it asks for a set of scopes, and the user (or the authorization server’s policy) decides whether to grant them. The operating principle is least privilege: request the narrowest scopes the app actually needs. Scope creep in a request is the OAuth equivalent of giving an intern root.

Refresh tokens: the long-lived half

Access tokens expire quickly, which would mean constant re-authorization. Refresh tokens solve that: a long-lived credential the client keeps offline and exchanges for fresh access tokens. Because a refresh token is powerful, its handling is the security-critical part — it should be stored like a password, rotated on use, and monitored for reuse. That is a topic worth its own article; the short version is that refresh tokens are where OAuth deployments actually get breached.

Flows: how the token is obtained

The way a client gets a token depends on the situation. The three you will meet most often:

  • Authorization code flow. The browser-based standard: the user approves in a browser, the app exchanges a short-lived code for tokens. Used by web and mobile apps.
  • Client credentials flow. No user involved: the app authenticates itself to get a token for its own access. Used for server-to-server calls.
  • Device flow (RFC 8628). For devices without a browser: the CLI or TV shows a code, the user visits a URL on their phone and approves, and the device polls until the token is ready. This is the flow that makes “log in with Google” work in a terminal.

What OAuth is not

OAuth 2.0 is delegation, not authentication. Knowing that a token is valid does not tell you who the user is — that is OpenID Connect’s job. Confusing the two is a common source of security bugs in integrations.

OAuth in practice for scripts and agents

Interactive web apps are well served by SDKs. Command-line tools and AI agents are where OAuth gets awkward: the browser dance, the token storage, and the refresh cycle all need to be handled by something that is not a web page. Tools that automate OAuth for the terminal exist for exactly this reason — trustless’s oauth command implements the device flow end-to-end for Google and Lark endpoints, storing tokens as compact single-line JSON entries with auto-refresh and single-use refresh-token rotation. The implementation is open source at trustless if you want to see the pattern in a small, dependency-light codebase.

The practical takeaway: tokens expire by design, scopes limit damage, and the flow you choose should match where the user is. Before you build another OAuth integration, ask yourself: which flow does this client actually need — and who holds the refresh token?