OAuth 2.0 & OpenID Connect Flows: A Quick Reference Guide

Admin
March 27, 2026 7:53 PM

If you support or implement OAuth 2.0 / OpenID Connect (OIDC) authorization servers, you've probably needed to quickly recall which flow uses a client secret, which one is safe for a browser app, or why the implicit grant keeps getting flagged in security reviews. This guide is a single-page reference for all of that.


The Decision Flowchart

Before diving into each flow, here's the quick decision tree:

Is a user involved?
├── NO  → Client Credentials Grant
└── YES
    ├── Does the device have a browser and keyboard?
    │   ├── NO  → Device Authorization Grant (Device Code Flow)
    │   └── YES
    │       ├── Need the user's identity (authentication)?
    │       │   ├── NO  → Authorization Code + PKCE (OAuth 2.0)
    │       │   └── YES → Authorization Code + PKCE (OIDC, scope=openid)
    │       └── Can the client securely store a secret?
    │           ├── YES → Confidential client: Auth Code + PKCE + client_secret
    │           └── NO  → Public client: Auth Code + PKCE (no client_secret)

Quick Comparison Matrix

Flow Client Type Requires Client Secret? Returns ID Token? Returns Refresh Token? Recommended?
Authorization Code Server-side web app Yes No Yes Yes
Auth Code + PKCE SPA, mobile, desktop, server Optional No Yes Yes — the default choice
Implicit SPA (legacy) No No No Deprecated
Client Credentials M2M, backend services Yes No No Yes (M2M only)
ROPC (Password) Legacy first-party Optional No Yes Deprecated
Device Code IoT, smart TV, CLI No (public) or Yes No Yes Yes (input-constrained)
Refresh Token Any (after initial grant) Yes (confidential) No Yes (rotation) Yes
OIDC Auth Code Server, SPA, mobile (+ PKCE) Optional Yes Yes Yes — default for OIDC
OIDC Implicit SPA (legacy) No Yes No Deprecated
OIDC Hybrid Server-side web apps Yes Yes Yes Legacy

OAuth 2.0 Grant Types in Detail

1. Authorization Code Grant

Status: Recommended  |  RFC: 6749 §4.1  |  Best for: Server-side web apps with a secure backend

Credentials involved: client_id, client_secret, authorization code (short-lived), access token, optional refresh token.

How it works

  1. Client redirects the browser to /authorize with response_type=code, client_id, redirect_uri, scope, and state.
  2. User authenticates and consents.
  3. Authorization server redirects back with an authorization code and state.
  4. Client validates state.
  5. Client makes a back-channel POST to /token with the code, client_id, client_secret, and redirect_uri.
  6. Authorization server returns an access token (and optionally a refresh token).

Key security notes: The access token is never exposed to the browser. The state parameter prevents CSRF. The client secret must be stored server-side only. Requires HTTPS everywhere.

2. Authorization Code + PKCE

Status: Required for public clients; required for ALL clients under OAuth 2.1  |  RFCs: 7636, OAuth 2.1, RFC 9700  |  Best for: SPAs, native mobile apps, desktop apps, and (under 2.1) all clients

Additional credentials: code_verifier (random 43-128 char string), code_challenge (Base64url SHA-256 of the verifier), code_challenge_method=S256.

How it works

  1. Client generates a cryptographically random code_verifier.
  2. Client computes code_challenge = BASE64URL(SHA256(code_verifier)).
  3. Client redirects to /authorize with all standard params plus code_challenge and code_challenge_method=S256.
  4. User authenticates and consents. Authorization server stores the challenge with the code.
  5. Client sends the code plus the original code_verifier to /token.
  6. Authorization server hashes the verifier, compares to the stored challenge. If matched, tokens are issued.

Key security notes: Even if an attacker intercepts the authorization code, they can't exchange it without the code_verifier (which never leaves the client). Always use S256, never plain in production.

3. Implicit Grant

Status: DEPRECATED — Removed from OAuth 2.1. RFC 9700 recommends against it.

Was designed for browser SPAs that couldn't make back-channel requests. Returns the access token directly in the URL fragment (#access_token=...). No authorization code, no refresh token, no client secret.

Why it's deprecated: Tokens leak through browser history, referrer headers, and extensions. No integrity verification. No refresh tokens. Authorization Code + PKCE does everything better.

4. Client Credentials Grant

Status: Recommended for M2M  |  RFC: 6749 §4.4  |  Best for: Server-to-server, daemons, microservices, CI/CD pipelines

Credentials: client_id + client_secret (or private_key_jwt, mTLS). No user involved.

How it works

  1. Client POSTs to /token with grant_type=client_credentials, credentials, and scope.
  2. Authorization server validates and returns an access token.

Key security notes: The secret must live in a secrets manager or environment variable — never in source code. The token represents the application, not a user. Use least-privilege scopes. Consider private_key_jwt or mTLS for stronger authentication.

5. Resource Owner Password Credentials (ROPC)

Status: DEPRECATED — Removed from OAuth 2.1.

The user gives their username and password directly to the client, which forwards them to /token. This fundamentally breaks the OAuth model (delegated authorization without sharing credentials). It can't support MFA or federated identity. Use Authorization Code + PKCE instead, even for first-party apps.

6. Device Authorization Grant (Device Code Flow)

Status: Recommended for input-constrained devices  |  RFC: 8628  |  Best for: Smart TVs, IoT devices, CLI tools, game consoles

Credentials: client_id, device_code, user_code, verification_uri.

How it works

  1. Device requests a device code from the authorization server.
  2. Server returns a device_code, user_code, verification_uri, and polling interval.
  3. Device displays: "Go to https://example.com/device and enter code: ABCD-1234"
  4. User opens a browser on a secondary device (phone/laptop), navigates to the URI, enters the code, authenticates, and consents.
  5. Meanwhile, the device polls /token with the device_code at the specified interval.
  6. Once the user completes authorization, polling returns an access token + refresh token.

Key security notes: The user code needs enough entropy to resist brute-force but be easy to type. Respect the polling interval. Watch for session fixation (an attacker could trick a user into authorizing a pre-generated device code).

7. Refresh Token Grant

Status: Recommended  |  RFC: 6749 §6  |  Best for: Any app that needs long-lived access without re-authentication

  1. Client detects the access token is expired (or about to expire).
  2. Client POSTs to /token with grant_type=refresh_token, the refresh token, and client credentials (if confidential).
  3. Authorization server validates and returns a new access token (and optionally a new refresh token via rotation).

Key security notes: Refresh token rotation is strongly recommended (RFC 9700) — each use invalidates the old token and issues a new one, allowing detection of stolen tokens. For public clients, rotation is essential. Set finite lifetimes. Consider sender-constraining with DPoP or mTLS.


OpenID Connect (OIDC) Flows

OIDC adds an identity layer on top of OAuth 2.0. The key addition is the ID Token — a JWT containing claims about the authenticated user. OIDC flows mirror OAuth 2.0 flows with these additions:

  • The scope includes openid (plus optionally profile, email, address, phone).
  • A nonce parameter prevents ID token replay attacks.
  • The token response includes an id_token (JWT).
  • A UserInfo endpoint is available for additional claims.

OIDC Authorization Code Flow

Status: Recommended  |  Spec: OIDC Core 1.0 §3.1

Identical to the OAuth 2.0 Authorization Code flow (with or without PKCE), but includes scope=openid, a nonce parameter, and returns an id_token alongside the access token. The client must validate the ID token: check iss, aud, exp, iat, nonce, and the signature (using the server's JWKS keys).

OIDC Implicit Flow

Status: DEPRECATED

Returns the ID token (and optionally an access token) directly in the URL fragment via response_type=id_token or response_type=id_token token. Same vulnerabilities as OAuth 2.0 Implicit. Use Auth Code + PKCE instead.

OIDC Hybrid Flow

Status: Legacy  |  Spec: OIDC Core 1.0 §3.3

Combines front-channel and back-channel token delivery using response types like code id_token, code token, or code id_token token. The ID token delivered on the front channel includes a c_hash (code hash) for integrity binding. Was useful when apps needed immediate identity info before completing the back-channel exchange, but Auth Code + PKCE is now fast enough to make this unnecessary.


Security Best Practices

Token Storage by Client Type

Client Type Access Token Refresh Token
Server-side web app Server-side session or encrypted cookie Server-side session or encrypted DB
SPA (browser) In-memory only (NOT localStorage) BFF pattern or secure httpOnly cookie; use rotation
Native mobile app OS secure storage (Keychain / Keystore) OS secure storage
M2M / daemon Env var or secrets manager N/A (just request a new token)

Never store tokens in localStorage or sessionStorage — any JavaScript on the page can read them (XSS risk).

Recommended Token Lifetimes

Token Type Recommended Lifetime Notes
Access token 5–15 minutes Short-lived limits theft damage
Refresh token Hours to days Never indefinite; use rotation
Authorization code 30 sec – 10 min Single-use; invalidate after exchange
ID token Minutes Authentication moment only; not a session token
Device code 5–15 minutes Enough for the user to complete on another device

Common Vulnerabilities & Mitigations

Vulnerability Mitigation
Authorization code interception Use PKCE
Token leakage via URL fragment Don't use Implicit; use Auth Code + PKCE
Open redirectors Exact redirect URI matching; pre-register all URIs
CSRF on callback state parameter + PKCE
Token injection / substitution Sender-constrained tokens (DPoP / mTLS); audience restriction
Refresh token theft Rotation, sender-constraining, secure storage
ID token replay nonce validation; aud validation
Mix-up attacks (multiple IdPs) Issuer identification (iss in auth response, RFC 9207)
JWT confusion (access token used as ID token) Validate typ header and aud claim
Token in query string Use Authorization header only (OAuth 2.1 prohibits query strings)

Redirect URI Rules

  • OAuth 2.1 requires exact string matching — no wildcards, no partial matches.
  • Pre-register all redirect URIs with the authorization server.
  • Never use open redirectors as redirect URIs.
  • Native apps: use custom URL schemes, claimed HTTPS links (Universal Links / App Links), or the loopback interface (http://127.0.0.1:{port}).

Sender-Constrained Tokens

DPoP (RFC 9449): Binds the access token to a client-held cryptographic key. Each API request includes a signed DPoP proof in the DPoP header. Stolen tokens are useless without the private key. Particularly valuable for SPAs using non-extractable keys via Web Crypto API.

mTLS (RFC 8705): Binds the token to the client's TLS certificate. More common in enterprise and B2B scenarios.


OAuth 2.1: What Changed

OAuth 2.1 (draft-ietf-oauth-v2-1) consolidates OAuth 2.0 and its security extensions into a single modernized spec. Key changes:

  1. PKCE is mandatory for all authorization code grants (public and confidential).
  2. Implicit grant is removed.
  3. ROPC grant is removed.
  4. Exact redirect URI matching is required.
  5. Bearer tokens in query strings are prohibited — use the Authorization header.
  6. Refresh token rotation is recommended for public clients.
  7. Sender-constrained tokens (DPoP, mTLS) are recommended.

RFC 9700 (published January 2025) serves as the authoritative Security BCP document, encoding many of these as MUST/SHOULD requirements.


References