Chapter 1 · Practice 2 of 4
Check new passwords against a blocklist of common and breached values
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
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));
}