v0.14 is out: a mobile auth pack for Swift/iOS and Android, catching insecure token storage, cleartext traffic, and OAuth in embedded WebViews. Read more →
HIGH AI PREVALENCE: HIGH auth.oauth.no-state

OAuth 2.0 authorization request is being built WITHOUT a state parameter.

Why AI tools produce this: AI coding tools generate this anti-pattern by default, it appears in a large share of AI-written auth code.

Why this matters

This opens you to CSRF attacks during the OAuth dance. An attacker can trick the victim into logging in as the attacker.

Always generate a cryptographically random state, store it in a session/cookie, and validate it on the callback. PKCE alone is not a substitute for state when handling browser sessions.

VULNERABLE
vulnerable.ts
// ruleid: auth.oauth.no-state
export const authorizeUrl = 'https://accounts.google.com/o/oauth2/v2/auth?client_id=abc&response_type=code&scope=openid%20email&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcb';

export function badRedirect(res: { redirect: (url: string) => void }) {
  // ruleid: auth.oauth.no-state
  const params = new URLSearchParams({
    client_id: 'abc',
    response_type: 'code',
    redirect_uri: 'https://app.example.com/cb',
    scope: 'openid email',
  });
  res.redirect(`https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`);
}

// Per-literal proof: this file mixes a CORRECT authorize URL (carries `state=`)
// with a broken one (no `state=`). The old file-level regex saw `state=`
// anywhere in the file and suppressed BOTH (a false negative). Only the
// state-less literal must flag now.
export const correctAuthorize =
  'https://accounts.google.com/o/oauth2/v2/auth?client_id=abc&response_type=code&state=xyz789&scope=openid';
// ruleid: auth.oauth.no-state
export const brokenAuthorize =
  'https://accounts.google.com/o/oauth2/v2/auth?client_id=abc&response_type=code&scope=openid';
SAFE
safe.ts
import { randomBytes } from 'node:crypto';

// ok: auth.oauth.no-state
export function goodRedirect(res: { redirect: (url: string) => void }) {
  const state = randomBytes(32).toString('hex');
  const params = new URLSearchParams({
    client_id: 'abc',
    response_type: 'code',
    redirect_uri: 'https://app.example.com/cb',
    scope: 'openid email',
    state,
  });
  res.redirect(`https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`);
}

Suppressing this rule

If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.

// oauthlint-disable-next-line auth.oauth.no-state -- <reason>

References

https://datatracker.ietf.org/doc/html/rfc6749#section-10.12 ↗