Authentication Best Practices

Chapter 1 · Practice 2 of 4

Check new passwords against a blocklist of common and breached values

From Password rules and storage

Why it matters

Length alone still lets through passwordpassword and passwords already leaked from other sites. NIST requires checking every new password against a blocklist of commonly used, expected or compromised values, comparing the whole password, and telling the user why it was refused.

Practical options:

  • A local list of the most common passwords, plus words specific to your product (its name, the domain).
  • A breached password service that supports k-anonymity range queries, where you send only the first characters of the password's hash and compare the rest locally, so the password never leaves your server.

When a password is rejected, say so plainly ("This password has appeared in a data breach, please choose another") so people pick something genuinely different instead of adding a 1.

Run the same check on password change and on reset, not only at sign-up.

Example

TypeScript
import { createHash } from 'node:crypto';

// k-anonymity: send only a short prefix of the SHA-1 hash
export async function isBreached(password: string): Promise<boolean> {
  const hash = createHash('sha1').update(password).digest('hex').toUpperCase();
  const prefix = hash.slice(0, 5);
  const suffix = hash.slice(5);
  const res = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`, {
    signal: AbortSignal.timeout(3000),
  });
  if (!res.ok) return false; // fail open, but log it
  const body = await res.text();
  return body.split('\n').some((line) => line.startsWith(suffix));
}