Credential Stuffing Defense: Block Breached Passwords
Your login endpoint is under attack right now, and not by someone guessing passwords. It's bots replaying known-valid username and password pairs leaked from other companies' breaches, betting that your users reused them. They're right often enough that credential stuffing is now the leading cause of account takeover — OWASP ranks authentication failures in its 2025 Top 10, and in November 2025 a single dataset added nearly two billion credentials to Have I Been Pwned. Some of your users' passwords are in there.
The highest-leverage defense against this is cheap, doesn't hurt UX, and most teams still don't do it: stop accepting passwords that are already in a breach. Here's why credential stuffing works, how to block breached passwords without ever seeing the password, and the layers that catch what slips through.
Credential stuffing, precisely
It's worth separating from brute force, because the defenses differ:
- Brute force guesses passwords against one account — many attempts, mostly wrong. Rate limiting and lockouts handle it.
- Credential stuffing replays already-valid pairs harvested from other breaches — often one attempt per account, spread across millions of accounts from a botnet of rotating residential IPs. Most attempts fail, but the ones that hit are correct on the first try, because the user reused that password.
That's the key insight: credential stuffing isn't a guessing problem, it's a password-reuse problem. The attacker already has a valid password — your job is to make sure it isn't valid here.
The highest-leverage defense: reject breached passwords
If reuse is the vulnerability, the most direct fix is to refuse passwords that are known to be compromised. At registration and password reset, check the chosen password against a corpus of breached passwords; if it appears, reject it and ask for another. A password that's already circulating in dumps is exactly the one a stuffing attack will try, so keeping it out of your system removes the attack's ammunition before it's ever loaded.
This does more than complexity rules ever did. "Must contain a number and a symbol" doesn't stop a user from picking Password@123 — which is in every breach corpus. Breach-checking does.
How to check without leaking the password: k-anonymity
The obvious objection: "I'm not sending my users' passwords to some third-party API." You don't have to — and you shouldn't. The standard approach, HIBP's Pwned Passwords range API, uses k-anonymity so the password (and even its full hash) never leaves your server:
1. Hash the candidate password with SHA-1. e.g. 5BAA61E4C9B93F3F...
2. Send ONLY the first 5 hex characters of the hash. → GET /range/5BAA6
3. The API returns every breached-hash SUFFIX that
starts with those 5 chars, with breach counts.
4. Compare locally: is your hash's suffix in the list?
→ yes: breached, reject. no: accept.
You transmit five characters of a hash. The API never sees the password, never sees the full hash, and can't tell which of the ~hundreds of returned suffixes (if any) was yours. The match happens entirely on your side. It's a single fast request at signup and reset, and it neutralizes the reuse that makes stuffing work.
You can also run a copy of the corpus locally if you'd rather make no external call at all — the same suffix-matching, fully offline.
Defense in depth
Breach-checking removes the easy wins, but assume some valid-and-not-yet-breached passwords will still be stuffed. Layer the rest:
- MFA. The single most effective backstop — a correct password alone isn't enough to get in. Prefer TOTP or passkeys over SMS; passkeys are phishing-resistant on top.
- Rate limiting. Stuffing is high-volume; layered per-account and per-IP limits slow it and make it expensive, even against rotating proxies.
- Anomaly signals. A login from a new device, a new geography, or a datacenter/residential-proxy ASN is a risk signal — step up to MFA or challenge it.
- Passkeys. The endgame: passwordless authentication means there's no reusable password to stuff in the first place.
No single layer is sufficient; together they turn account takeover from "one leaked password away" into a real obstacle course.
What not to do
- Don't rely on complexity rules. They produce predictable patterns that are in the breach corpus and annoy users without stopping reuse.
- Don't store the password to check it. Hash it, use k-anonymity; the password should never be persisted or sent anywhere in the clear.
- Don't fail silently or cryptically. Tell the user plainly: "This password appeared in a known data breach — please choose a different one." A clear message converts; a generic "invalid password" frustrates.
- Don't lock accounts on stuffing. Mass lockouts from a stuffing run become a denial-of-service against your real users. Throttle and challenge instead of locking.
How NamoID layers it
NamoID treats account takeover as a stack, not a single switch: new passwords are checked against known-breached corpora using k-anonymity at registration and reset (the password never leaves your boundary), MFA via TOTP and passkeys backs up the password, Redis-backed rate limits throttle high-volume attempts, and every limit trip and suspicious event is written to the append-only audit trail so a stuffing campaign is visible rather than silent. The breach check stops the cheap wins; MFA and rate limits absorb the rest; passkeys remove the password from the equation for users who adopt them.
FAQ
Isn't sending passwords to a breach API a privacy risk? You don't send passwords — k-anonymity sends only the first five characters of a SHA-1 hash, and the match happens locally. The service never sees the password or its full hash. You can also run the corpus offline.
Will blocking breached passwords annoy users? Less than you'd think, if the message is clear. "This password was in a breach, pick another" is understandable; users generally accept it. It's far less annoying than getting their account taken over.
Is MFA enough on its own? MFA is the strongest single layer, but pair it with breach-checking and rate limits — defense in depth means an attacker has to beat several controls, not one.
Do passkeys make this moot? For users who adopt passkeys, yes — there's no reusable password to stuff. Until then, breach-checking plus MFA protects the password users you still have.
Take away the attacker's ammunition
Credential stuffing works because of reused, already-leaked passwords — so the most effective thing you can do is refuse to accept them, which k-anonymity lets you do without ever handling the password unsafely. Layer MFA, rate limiting, and anomaly detection behind that, and move users toward passkeys, and the leading cause of account takeover stops being a one-password-away risk.
NamoID ships these as defaults — breached-password detection, MFA and passkeys, rate limiting, and an audit trail of the attacks it absorbs — behind one identity layer. If your login endpoint is taking stuffing traffic (it is), that's the stack that turns it into noise.