# OAuth 2.0 & OpenID Connect security review checklist

> An OAuth 2.0 / OIDC security checklist based on RFC 9700: PKCE everywhere, exact redirect URI matching, strict JWT validation, token storage, refresh token rotation and sender-constrained tokens.

By George Rios (geearr) · 2026-10-12 · https://geearr.com/guides/oauth-oidc-security-review

**Short answer:** use the authorization code flow with PKCE for every client, match redirect URIs exactly, validate every token strictly (algorithm, signature, issuer, audience, expiry), keep tokens out of browser storage where you can, rotate refresh tokens with reuse detection, and scope everything to least privilege. RFC 9700 — *Best Current Practice for OAuth 2.0 Security* — is the reference this checklist follows.

## Flows

- [ ] **Authorization code + PKCE** for all clients: SPAs, mobile, server-side web apps and CLIs. PKCE (RFC 7636) with the `S256` method, never `plain`.
- [ ] **No implicit flow.** Tokens in URL fragments leak through history, referrers and logs.
- [ ] **No resource owner password credentials grant.** It trains users to type passwords into third-party apps and breaks MFA.
- [ ] Client credentials only for genuine machine-to-machine calls, with short-lived tokens.
- [ ] Device authorization grant for input-constrained devices only.

## Redirect URIs and request integrity

- [ ] Redirect URIs are registered and compared by **exact string match** — no wildcards, no prefix matching, no open redirects on the redirect target.
- [ ] `state` (or PKCE) prevents CSRF on the callback; `nonce` binds the ID token to the session in OIDC.
- [ ] Clients that talk to more than one authorization server check the `iss` parameter in the authorization response (RFC 9207) to prevent mix-up attacks.
- [ ] Consider pushed authorization requests (PAR, RFC 9126) for high-value clients so parameters cannot be tampered with in the browser.

## Token validation

Every resource server and client that consumes a JWT must:

- [ ] Accept only an explicit **allowlist of algorithms** (for example `RS256`, `ES256`). Reject `none`. Never let the token’s header choose an HMAC algorithm keyed with a public key.
- [ ] Verify the **signature** against keys fetched from the issuer’s JWKS, selected by `kid`, with caching and rotation handled.
- [ ] Check **`iss`**, **`aud`** (this API, not just any API from the same issuer), **`exp`**, **`nbf`** and **`iat`**, with small clock skew.
- [ ] Treat **ID tokens as login proof for the client only** — never as API access tokens.
- [ ] Keep access tokens short-lived (minutes, not days).

## Token storage and transport

- [ ] For browser apps, prefer a **backend-for-frontend (BFF)**: tokens stay on the server, the browser gets an `HttpOnly`, `Secure`, `SameSite` session cookie.
- [ ] If tokens must live in the browser, keep them in memory, not `localStorage`, and enforce a strict Content Security Policy.
- [ ] **Refresh token rotation with reuse detection**: each use issues a new refresh token, and reuse of an old one revokes the whole family.
- [ ] Sender-constrained tokens — DPoP (RFC 9449) or mutual TLS (RFC 8705) — for high-risk APIs, so a stolen token is useless elsewhere.
- [ ] No client secrets in mobile apps, SPAs or anything shipped to users. Those are public clients.

## Scopes, consent and authorization

- [ ] Scopes are **narrow and purpose-specific**; tokens are requested with the minimum needed.
- [ ] The API enforces authorization on **every request and every object** — a valid token does not mean access to every record (BOLA).
- [ ] Consent screens accurately describe what is granted; users and admins can review and revoke grants.

## Keys and the authorization server

- [ ] Signing keys are at least RSA-2048 or P-256, stored in an HSM or KMS where possible, and **rotated** with overlap published in JWKS.
- [ ] Discovery (`/.well-known/openid-configuration`) advertises only the flows and algorithms you actually support.
- [ ] Login endpoints are rate-limited and protected against credential stuffing; MFA and passkeys are available.

## Session lifecycle

- [ ] Logout ends the local session **and** the identity-provider session where intended (RP-initiated, front-channel or back-channel logout).
- [ ] Revocation (RFC 7009) works for refresh tokens, and introspection is available where opaque tokens are used.
- [ ] Account deletion and offboarding revoke all outstanding grants.

## Test it like an attacker

Swap the `aud` to a different API. Replay an old refresh token. Change the redirect URI by one character. Change `alg` to `none`. Use user A’s token against user B’s resource. If any of those work, you have found your first fix.

geearr reviews OAuth and OIDC implementations as part of [application security engagements](/services/application-security) — informed by building [AuthnzNet](https://authnz.net), an OAuth 2.0 + OIDC server, and [Identizen](https://identizen.com), an accountless OIDC identity provider.

---
Canonical: https://geearr.com/guides/oauth-oidc-security-review · Updated: 2026-10-12 · Site index for LLMs: https://geearr.com/llms.txt
