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: HIGH auth.oauth.no-pkce

OAuth authorization request from a public client omits the PKCE code_challenge 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

The request looks like a public client (SPA, mobile, or native app) yet carries no code_challenge. Without PKCE, the authorization code can be intercepted and exchanged by an attacker.

RFC 8252 §6 mandates PKCE for native/SPA clients. RFC 9700 (OAuth 2.0 Security BCP) recommends PKCE for ALL clients, including confidential ones, as defence in depth.

Generate a code_verifier (43-128 char), derive code_challenge = BASE64URL-NoPad(SHA256(code_verifier)), send it with code_challenge_method=S256 on the authorize call, and POST the code_verifier on the token call.

VULNERABLE
vulnerable.ts
// ruleid: auth.oauth.no-pkce
export const authorizeUrl =
  'https://accounts.google.com/o/oauth2/v2/auth?client_id=spa-app&response_type=code&scope=openid&state=abc';

export function badRedirect(res: { redirect: (url: string) => void }, state: string) {
  // ruleid: auth.oauth.no-pkce
  const params = new URLSearchParams({
    client_id: 'spa-app',
    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()}`);
}
SAFE
safe.ts
import { createHash, randomBytes } from 'node:crypto';

// ok: auth.oauth.no-pkce
export function goodRedirect(
  res: { redirect: (url: string) => void },
  state: string,
  verifier: string,
) {
  const challenge = createHash('sha256').update(verifier).digest('base64url');
  const params = new URLSearchParams({
    client_id: 'spa-app',
    response_type: 'code',
    redirect_uri: 'https://app.example.com/cb',
    scope: 'openid email',
    state,
    code_challenge: challenge,
    code_challenge_method: 'S256',
  });
  res.redirect(`https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`);
}

export function newVerifier() {
  return randomBytes(32).toString('base64url');
}

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-pkce -- <reason>

References

https://datatracker.ietf.org/doc/html/rfc7636 ↗https://datatracker.ietf.org/doc/html/rfc8252#section-6 ↗