OAuth + OIDC security — the PKCE and state checks you cannot skip
Most OAuth bugs come from skipping PKCE, ignoring state, or accepting tokens issued for a different client. Here is the correct implementation in a Next.js + NextAuth app.
OAuth and OIDC are secure protocols with many unsafe implementations. The safe version requires PKCE for public clients, state for CSRF, nonce for OIDC, and token-audience verification.
What it is
OAuth 2.0 grants an app delegated access to a resource on behalf of a user. OIDC builds identity on top. Secure use requires the small set of checks below.
Vulnerable example
// Vulnerable: no state, no PKCE, no audience check on ID token
const auth_url = `https://provider.com/authorize?client_id=${id}&redirect_uri=${redir}&response_type=code`;
// redirect user; receive code; exchange for tokens; decode JWT without verifying audience.Fixed example
// Fixed: PKCE + state + nonce + audience check
import { randomBytes, createHash } from "crypto";
const state = randomBytes(16).toString("base64url");
const nonce = randomBytes(16).toString("base64url");
const code_verifier = randomBytes(32).toString("base64url");
const code_challenge = createHash("sha256").update(code_verifier).digest("base64url");
// store state, nonce, code_verifier in session
// include state, nonce, code_challenge_method=S256, code_challenge in authorize URL
// on callback: verify state matches, exchange code with code_verifier,
// verify id_token signature, audience, issuer, nonceHow Securie catches it
apps/web/app/api/route.ts:22OAuth + OIDC security
Securie audits every OAuth implementation in the repository — NextAuth, Clerk, custom, Lucia — requiring PKCE, state, and explicit audience verification.
// Fixed: PKCE + state + nonce + audience check
import { randomBytes, createHash } from "crypto";
const state = randomBytes(16).toString("base64url");
const nonce = randomBytes(16).toString("base64url");
const code_verifier = randomBytes(32).toString("base64url");
const code_challenge = createHash("sha256").update(code_verifier).digest("base64url");
// store state, nonce, code_verifier in session
// include state, nonce, code_challenge_method=S256, code_challenge in authorize URL
// on callback: verify state matches, exchange code with code_verifier,
// verify id_token signature, audience, issuer, nonceChecklist
- PKCE enforced for all public clients (required for SPAs and mobile)
- State parameter included and validated on callback
- OIDC nonce included in id_token and verified
- ID token audience + issuer verified
- Access token never passed through a query parameter (use Authorization header)
- Refresh tokens rotated with reuse detection
FAQ
If I use NextAuth, do I need to do any of this?
NextAuth does most of it. Review that your provider config sets the right scopes and that any custom providers inherit the safe defaults.
Related guides
If your webhook endpoint skips signature verification, an attacker can trigger any downstream action you code — refunds, subscription changes, user upgrades. Here is how to verify signatures correctly for the five most common webhook providers.
Misconfigured cookies are how session tokens leak. Here is exactly which flags to set for session, CSRF, and preference cookies in a Next.js app.
JWTs are only as secure as your verification. Missing issuer check, missing expiration check, `alg: 'none'`, and algorithm confusion all still ship in AI-generated code.
A logged-in user is a trust decision you made at login. Sessions need explicit policies for idle timeout, absolute timeout, revocation, and rotation.