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.pkce-plain

PKCE is configured with code_challenge_method=plain.

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 plain method sends the code_verifier itself as the code_challenge, so an attacker who intercepts the authorization request learns the verifier and PKCE provides no protection against authorization-code interception.

Use code_challenge_method=S256: derive code_challenge = BASE64URL-NoPad(SHA256(code_verifier)) (RFC 7636 §4.2). S256 is mandatory for clients that can compute SHA-256.

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

export function badPkceObject(verifier: string) {
  // ruleid: auth.oauth.pkce-plain
  const params = new URLSearchParams({
    client_id: 'spa-app',
    response_type: 'code',
    state: 'abc',
    code_challenge: verifier,
    code_challenge_method: 'plain',
  });
  return params.toString();
}

export function badPkceAppend(verifier: string) {
  const params = new URLSearchParams();
  params.set('client_id', 'spa-app');
  params.set('response_type', 'code');
  params.set('code_challenge', verifier);
  // ruleid: auth.oauth.pkce-plain
  params.append('code_challenge_method', 'plain');
  return params.toString();
}
SAFE
safe.ts
import { createHash } from 'node:crypto';

// ok: auth.oauth.pkce-plain
export const authorizeUrl =
  'https://accounts.google.com/o/oauth2/v2/auth?client_id=spa-app&response_type=code&state=abc&code_challenge=xyz&code_challenge_method=S256';

// ok: auth.oauth.pkce-plain
export function goodPkceObject(verifier: string) {
  const challenge = createHash('sha256').update(verifier).digest('base64url');
  const params = new URLSearchParams({
    client_id: 'spa-app',
    response_type: 'code',
    state: 'abc',
    code_challenge: challenge,
    code_challenge_method: 'S256',
  });
  return params.toString();
}

// ok: auth.oauth.pkce-plain
export function noPkce(state: string) {
  // No PKCE at all — out of scope for this rule (see auth.oauth.no-pkce).
  const params = new URLSearchParams({
    client_id: 'spa-app',
    response_type: 'code',
    state,
  });
  return 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.pkce-plain -- <reason>

References

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