JustAppSec
Back to guides

OAuth 2.0 Security Best Practices

OAuth 2.0 is the standard for delegated authorization, but the spec is flexible enough to be implemented insecurely. This guide covers what goes wrong in practice and how to avoid it.

Always Use PKCE

Proof Key for Code Exchange (PKCE) prevents authorization code interception attacks. Originally designed for mobile apps, it is now recommended for all OAuth clients, including server-side web apps.

import crypto from "crypto";

// Generate code verifier (random string)
const codeVerifier = crypto.randomBytes(32).toString("base64url");

// Generate code challenge (SHA-256 hash of verifier)
const codeChallenge = crypto
  .createHash("sha256")
  .update(codeVerifier)
  .digest("base64url");

// Send code_challenge in authorization request
// Send code_verifier in token exchange

Reference: RFC 7636 — PKCE

The current best practice document from the IETF recommends PKCE for all clients and requires authorization servers to support it:

Reference: OAuth 2.0 Security Best Current Practice (RFC 9700)

Validate Redirect URIs Strictly

Open redirect vulnerabilities in OAuth are devastating — they allow token theft.

  • Register exact redirect URIs. No wildcards.
  • Match the full URI including path. Do not match on domain alone.
  • Never allow user-supplied redirect URIs without strict allowlist validation.

Bad:

redirect_uri=https://example.com/callback  ✓
redirect_uri=https://example.com.evil.com  ✗ (but may pass loose matching)
redirect_uri=https://example.com/callback/../evil  ✗ (path traversal)

Reference: OAuth 2.0 — Redirect URI Validation (RFC 6749 §3.1.2)

Use the Authorization Code Flow

For server-side apps, always use the Authorization Code flow (with PKCE). Never use the Implicit flow — it exposes tokens in the URL fragment and has been deprecated.

Reference: OAuth 2.0 Security BCP — Implicit Flow (RFC 9700)

Validate the state Parameter

The state parameter prevents CSRF attacks on the OAuth flow:

  1. Generate a random, unguessable state value.
  2. Store it in the user's session before redirecting to the authorization server.
  3. Verify it matches when the user returns to your callback.
import crypto from "crypto";

// Before redirect
const state = crypto.randomBytes(16).toString("hex");
session.oauthState = state;

// In callback
if (req.query.state !== session.oauthState) {
  throw new Error("Invalid state — possible CSRF");
}
delete session.oauthState;

Token Storage

  • Access tokens: short-lived (minutes), stored in memory or HttpOnly cookies.
  • Refresh tokens: stored server-side (database, encrypted cookie). Never in localStorage.
  • ID tokens (OIDC): validate signature and claims, then extract user info. Do not use as access tokens.

Validate ID Tokens Properly (OIDC)

If using OpenID Connect, validate the ID token:

  • Verify the signature against the provider's JWKS endpoint.
  • Check iss matches the provider.
  • Check aud matches your client ID.
  • Check exp has not passed.
  • Check nonce matches what you sent (prevents replay).

Reference: OpenID Connect Core — ID Token Validation

Scope Minimization

Request only the scopes you need. Broad scopes increase the blast radius if a token is compromised.

// BAD
scope=openid profile email admin read write

// GOOD
scope=openid email

Token Lifetime and Rotation

  • Access tokens: 5–15 minutes.
  • Refresh tokens: rotate on every use (issue a new refresh token with each access token refresh).
  • Detect refresh token reuse — if a refresh token is used twice, revoke the entire token family.

Reference: OAuth 2.0 Security BCP — Refresh Token Protection (RFC 9700)

Prevent Token Leakage

  • Do not pass tokens in URL query parameters (they end up in logs and referrer headers).
  • Use the Authorization: Bearer header for API calls.
  • Set Referrer-Policy: strict-origin-when-cross-origin to prevent token leakage via referrer.
  • Do not log tokens.

Provider-Specific Hardening

Google

  • Verify tokens using Google's tokeninfo endpoint or JWKS: Google — Verify ID Tokens
  • Use hd parameter to restrict to specific Google Workspace domains.

Microsoft / Entra ID

GitHub

Common Mistakes

  • Not using PKCE (even on backend apps).
  • Loose redirect URI matching.
  • Using Implicit flow.
  • Storing tokens in localStorage.
  • Not validating state on callback.
  • Requesting excessive scopes.
  • Using ID tokens as access tokens.

Related Guides


Content is AI-assisted and reviewed by our team, but issues may be missed and best practices evolve rapidly, send corrections to [email protected]. Always consult official documentation and validate key implementation decisions before making design or security choices.

Need help?Get in touch.