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 →
MEDIUM AI PREVALENCE: MEDIUM auth.oauth.no-nonce

OIDC authorization request (scope contains openid) is being built WITHOUT a nonce parameter.

Why AI tools produce this: AI coding tools produce this regularly, typically when prompted for a shortcut or a quick fix.

Why this matters

The nonce binds the id_token to this specific authorization request. Without it, an attacker can replay or substitute a stolen/forged id_token (OIDC Core §3.1.2.1).

Generate a cryptographically random nonce, store it in the session/cookie, send it on the authorize call, and verify the nonce claim in the returned id_token. It is REQUIRED for the implicit and hybrid flows and RECOMMENDED for the authorization-code flow.

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

// ruleid: auth.oauth.no-nonce
export const implicitUrl =
  'https://login.example.com/authorize?client_id=spa&response_type=id_token&scope=openid&state=xyz';

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

export function badHybridRedirect(res: { redirect: (url: string) => void }, state: string) {
  // ruleid: auth.oauth.no-nonce
  const params = new URLSearchParams({
    client_id: 'spa',
    response_type: 'code id_token',
    scope: 'openid',
    state,
  });
  res.redirect(`https://login.example.com/authorize?${params.toString()}`);
}
SAFE
safe.ts
import { randomBytes } from 'node:crypto';

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

// ok: auth.oauth.no-nonce
// OIDC inline URL that already carries a nonce.
export const oidcUrlWithNonce =
  'https://login.example.com/authorize?client_id=spa&response_type=id_token&scope=openid&state=xyz&nonce=n-0S6_WzA2Mj';

// ok: auth.oauth.no-nonce
// Pure OAuth 2.0 — no `openid` scope, so this is NOT an OIDC request and a
// nonce is not applicable.
export const oauthOnlyUrl =
  'https://github.com/login/oauth/authorize?client_id=abc&response_type=code&scope=repo%20user&state=xyz';

// ok: auth.oauth.no-nonce
// Pure OAuth 2.0 via URLSearchParams — scope has no `openid`.
export function oauthOnlyRedirect(res: { redirect: (url: string) => void }, state: string) {
  const params = new URLSearchParams({
    client_id: 'abc',
    response_type: 'code',
    redirect_uri: 'https://app.example.com/cb',
    scope: 'repo user',
    state,
  });
  res.redirect(`https://github.com/login/oauth/authorize?${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-nonce -- <reason>

References

https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest ↗