Guide
OAuth 2.0 and OpenID Connect explained
"Sign in with Google" is everywhere — but most developers confuse what OAuth actually does. OAuth 2.0 is an authorization framework: it lets a user grant a third-party application limited access to resources on another service, without handing over their password. OpenID Connect (OIDC) layers on top of OAuth to answer a different question: who is this user? — adding standardized identity tokens and a discovery document so clients do not reinvent login. Mixing the two, skipping PKCE on public clients, or treating OAuth scopes as your entire permission model are how production breaches start. This guide walks through the roles (resource owner, client, authorization server, resource server), the flows you should actually use in 2026, how access tokens differ from ID tokens, scope design, refresh-token rotation, redirect URI pitfalls, and a checklist before you ship "Login with X" to real users.
Authorization vs authentication — and where OIDC fits
OAuth's core job is delegated authorization. A photo-printing app should be able to read albums from a cloud drive because the user clicked "Allow" — not because the app stores the user's Google password. The authentication vs authorization distinction matters: OAuth issues tokens that prove the user (or client) was granted certain access rights. It does not, by itself, standardize how you learn the user's display name or email.
OpenID Connect fixes that gap. Built on OAuth 2.0, OIDC adds an
ID token (a signed
JWT
with claims like sub, email, and name),
a userinfo endpoint, and a /.well-known/openid-configuration
discovery URL so clients auto-configure endpoints. When product copy says "Sign
in with GitHub," you are almost always running an OIDC-flavored authorization
code flow, not "pure" resource delegation.
The four roles in every OAuth deployment
Every OAuth conversation involves four parties. Naming them correctly prevents architecture mistakes:
- Resource owner — the human (or organization) who can grant access. Usually the logged-in user at the identity provider.
- Client — the application requesting access: your SPA, mobile app, or backend service.
- Authorization server — issues tokens after authenticating the user and recording consent. Examples: Auth0, Okta, Keycloak, Cognito, or Google Accounts.
- Resource server — the API that holds protected data (Google Drive, GitHub repos, your own REST API). It validates access tokens on each request.
In many SaaS setups the authorization server and resource server are the same vendor (Google issues tokens and hosts Gmail). In microservice architectures they split: your auth service mints tokens; individual services validate them at the API gateway or via shared JWKS keys.
OAuth flows — which one to use
OAuth 2.1 (the consolidated best-practice spec) narrows the menu. Deprecated patterns (implicit flow, password grant for third parties) should not appear in new systems. The decision table below reflects what security reviewers expect in 2026:
Authorization code + PKCE (default for user login)
The browser or app redirects the user to the authorization server, the user
signs in and consents, and the server redirects back with a one-time
code. The client exchanges that code at the token endpoint for
access (and optionally refresh) tokens. PKCE (Proof Key for
Code Exchange) adds a code_verifier / code_challenge
pair so intercepted authorization codes cannot be redeemed by an attacker —
mandatory for public clients (SPAs, mobile) and recommended everywhere.
- Client generates random
code_verifier, hashes tocode_challenge. - Redirect user to
/authorizewithresponse_type=code,client_id,redirect_uri,scope,state, and PKCE challenge. - User authenticates; authorization server redirects to
redirect_uri?code=...&state=... - Client verifies
statematches (CSRF protection), POSTs to/tokenwithcode,code_verifier, and client authentication if confidential. - Authorization server returns access token (+ refresh token, ID token if OIDC).
Client credentials (machine-to-machine)
No user in the loop. A confidential backend client authenticates with
client_id + client_secret (or mTLS / private-key JWT)
and receives an access token scoped to service permissions. Use for cron jobs,
ETL pipelines, and internal microservices — never embed the secret in a mobile
app or browser bundle. Store credentials in a
secrets manager,
rotate regularly, and scope tokens to the minimum API surface.
Device authorization grant (TVs and CLI tools)
For input-constrained devices: the client displays a user code and URL; the user completes login on a phone or laptop. Polling the token endpoint returns tokens once the user approves. Essential for smart TVs, IoT panels, and developer CLIs where embedding a full browser OAuth redirect is awkward.
Flows to avoid
- Implicit flow — tokens returned in the URL fragment; leaked via referrer headers and browser history. Removed from OAuth 2.1.
- Resource owner password credentials — your app collects the user's IdP password. Only acceptable for first-party migration; never for third-party integrations.
- Authorization code without PKCE on SPAs — treat as a vulnerability waiting for a malicious redirect handler.
Access tokens vs ID tokens
Teams routinely misuse token types. Keep the contract straight:
- Access token — presented to resource servers in the
Authorization: Bearerheader. Proves the holder may perform actions within granted scopes until expiry. May be opaque or a JWT; validation is the resource server's job (signature, issuer, audience, expiry, scope). - ID token — OIDC only; intended for the client to
learn who authenticated. Contains identity claims (
sub, name, email). Never send an ID token to your product API as if it were an access token — APIs should reject tokens with wrongaud(audience) or missing resource indicators. - Refresh token — long-lived secret used only at the token endpoint to obtain new access tokens without re-prompting the user. Treat as highly sensitive; bind to client, rotate on each use, and revoke on logout.
A SPA that stores access tokens in localStorage is vulnerable to
XSS exfiltration. Prefer backend-for-frontend (BFF) patterns: the browser holds
an HttpOnly session cookie; the BFF holds refresh tokens server-side and attaches
short-lived access tokens to upstream calls. Pair with strict Content Security
Policy — see
CSRF and XSS defenses.
Scopes — consent boundaries, not your entire AuthZ model
Scopes are strings the user approves on the consent screen:
openid profile email, read:orders,
repo:write. They define what the token may request from
the authorization server's APIs — not automatically what your application's
internal roles allow.
Good scope design is coarse at the OAuth layer, fine inside your app:
- Request the minimum scopes at login — add incremental consent when the user triggers a feature that needs more (e.g. calendar write only when they enable scheduling).
- Document each scope in your OpenAPI spec if you expose third-party developer APIs.
- Enforce row-level authorization in your service even when the token carries
admin— stolen tokens should have bounded blast radius. - Standard OIDC scopes:
openid(required for ID token),profile,email,address,phone. Custom API scopes should be namespaced (https://api.example.com/orders.read) to avoid collisions.
Redirect URIs, state, and common misconfigurations
Redirect URI validation is the top OAuth misconfiguration in breach reports. Attackers register lookalike apps or exploit loose matching to steal authorization codes.
- Exact match only — register full URIs; no wildcard subdomains unless you fully control the parent domain and understand the risk.
- HTTPS everywhere — except
http://127.0.0.1for local dev with fixed ports. Neverhttp://production callbacks. - Validate
state— cryptographically random, stored server-side or in sealed cookie; reject callbacks with missing or wrong state to block login CSRF. - Validate
nonce(OIDC) — bind ID token to the original auth request; reject replayed ID tokens. - Check
iss(issuer) — accept tokens only from your configured authorization server; prevents token substitution from a rogue IdP. - Audience (
aud) — access tokens must list your API as intended recipient; ID tokens list yourclient_id. - Clock skew — allow small tolerance on
exp/nbfbut reject expired tokens; sync NTP on validators.
Multi-tenant SaaS adds complexity: each customer might bring their own IdP
(enterprise SAML/OIDC federation). Centralize issuer allowlists, map external
sub claims to internal user IDs, and never trust email alone as a
stable identifier — it can change or be unverified.
Token lifecycle — refresh, revocation, and logout
Short-lived access tokens (5–15 minutes) limit damage from leakage. Refresh tokens enable silent renewal but require discipline:
- Rotation — each refresh returns a new refresh token and invalidates the old one; detect reuse (possible theft) and revoke the whole family.
- Revocation endpoint — call on explicit logout; clear client storage and server-side sessions.
- Back-channel logout (OIDC) — IdP notifies clients when the user signs out globally; implement if your threat model includes shared computers.
- Session management — "logout" in your app must mean more than deleting a cookie; invalidate refresh tokens at the authorization server.
For high-assurance apps (payments, key export), combine OAuth with step-up authentication — require fresh MFA even when the access token is valid.
Choosing and operating an identity provider
Build vs buy is a real tradeoff. Managed IdPs (Auth0, Clerk, Cognito, Firebase Auth, Azure AD B2C) ship OIDC discovery, consent UI, breach monitoring, and compliance artifacts. Self-hosted Keycloak or Ory gives control and data residency at the cost of patching, scaling, and key ceremony.
Evaluation criteria:
- OIDC compliance and PKCE support out of the box
- Social and enterprise federation (SAML/OIDC upstream)
- Custom claim mapping and rules for multi-tenant apps
- Rate limits and SLA on the token endpoint (your login depends on it)
- Audit logs for consent grants and admin changes
- Data processing agreements if you handle EU users (GDPR)
Whichever you choose, treat the authorization server as tier-zero infrastructure — downtime means nobody logs in, and a compromise hands attackers the keys to mint valid tokens across your fleet.
Flow selection decision table
| Scenario | Recommended flow | Notes |
|---|---|---|
| Web or mobile app — user login | Authorization code + PKCE | BFF or native secure storage for tokens; avoid implicit |
| Backend service calling APIs | Client credentials | Secret in vault; narrow scopes; no user context |
| Smart TV, printer, CLI | Device authorization grant | Poll with backoff; short user-code TTL |
| First-party migration only | Password grant (legacy) | Plan removal; never for third-party clients |
| SPA without backend | Auth code + PKCE via lightweight BFF | Do not store refresh tokens in browser storage long-term |
Production checklist
- Use authorization code + PKCE for all public clients; disable implicit flow.
- Register exact redirect URIs; reject unknown callbacks.
- Validate
state,nonce(OIDC),iss,aud, andexpon every token. - Keep access tokens short-lived; rotate refresh tokens; revoke on logout.
- Separate ID tokens (client identity) from access tokens (API authorization).
- Enforce app-level AuthZ after token validation — scopes are not enough alone.
- Store client secrets and refresh tokens in a secrets manager, never in git.
- Log auth events (login, consent, refresh, revoke) without logging token values.
- Document scopes in your API contract; review quarterly for scope creep.
- Run annual redirect-URI and client inventory audits; delete unused OAuth apps.
Key takeaways
- OAuth delegates access; OIDC adds standardized login with ID tokens and discovery.
- Authorization code + PKCE is the default for user-facing apps in 2026.
- Access tokens go to APIs; ID tokens stay with the client — do not swap them.
- Scopes are consent boundaries, not a replacement for your permission model.
- Redirect URI exactness and token validation prevent the most common real-world OAuth breaches.
Related reading
- Authentication vs authorization explained — AuthN, AuthZ, RBAC, and where OAuth scopes fit
- JWT explained — signing algorithms, claims validation, and token storage pitfalls
- Passkeys and WebAuthn explained — phishing-resistant authentication alongside OIDC
- API gateway explained — terminating OAuth tokens at the edge vs per-service validation