Credential stuffing is the quiet workhorse of account takeover. Attackers take username and password pairs leaked from unrelated breaches and replay them against your login form, betting on the fact that people reuse passwords. They are not guessing. For every pair they try, the password is already correct somewhere — they just need to find where it also works. That single fact makes the attack hard to catch with tools built for brute force, because most stuffing traffic uses the right password on the first try.
The defensive problem is separating replayed stolen credentials from the genuine returning user who also, legitimately, typed their correct password. The answer is never a single rule. It is a layered set of signals scored together, tuned so that real users almost never feel friction and automated replay almost always does.
Why network rules alone miss it
The instinct is to rate-limit by IP and block the noisy ones. Modern stuffing defeats this by design. Credential lists are run through residential proxy networks that spread requests across thousands of consumer IP addresses, so no single address is noisy. Attackers also run low-and-slow, pacing attempts under any per-IP threshold you set.
The result: at the network layer, an attack can look like a modest trickle of successful logins from ordinary broadband addresses. You need signals that survive IP rotation. Two categories matter most:
- Device continuity. A device fingerprint persists even as the IP changes every request, so one automated toolchain touching hundreds of accounts is visible as one device.
- Behavioral fidelity. Automated form submission produces mouse and keystroke patterns that differ sharply from a human filling a login form.
The signals that actually separate replay from real logins
No single indicator is conclusive; the discrimination comes from combining them. The strongest, roughly in order of value:
- Fingerprint-to-account fan-out. One device attempting logins across many unrelated accounts is the clearest stuffing tell. Honest users have one or two accounts, not fifty.
- Failure-rate spikes. Even a good credential list is mostly stale. A window where failures jump well above baseline signals bulk replay, since your normal users mostly succeed.
- New device on a known account. A correct password arriving from a device you have never seen for that account is the new-device login case and deserves a step-up.
- Automation traits. Headless Chrome markers, Selenium and Puppeteer instrumentation, and missing behavioral entropy flag scripted submission.
- Impossible travel. The same credential succeeding from two distant geographies within minutes is a strong impossible-travel signal.
- Proxy and datacenter context. Proxy detection and datacenter IP enrichment add risk weight even when the IP itself is not rate-limited.
Scoring instead of blocking
Because a correct password from a real user and a correct password from an attacker can look identical on any one signal, the right architecture assigns a risk score per attempt and lets the score drive the response. A suspect score with reason codes makes the decision explainable and tunable.
A simplified scoring pass at the login endpoint:
def login_risk(attempt, device, account):
score = 0
reasons = []
if device.accounts_touched_24h > 10:
score += 40; reasons.append("high_account_fanout")
if not account.has_seen_device(device.id):
score += 20; reasons.append("new_device")
if device.automation_signals:
score += 25; reasons.append("automation_detected")
if attempt.ip_is_datacenter or attempt.ip_is_proxy:
score += 15; reasons.append("suspect_network")
if account.impossible_travel(attempt):
score += 30; reasons.append("impossible_travel")
return score, reasons
Map the score to graduated responses rather than a hard allow/deny:
| Score band | Response |
|---|---|
| 0-20 | Allow, log for baseline |
| 21-50 | Step-up: MFA or email verification |
| 51-75 | Challenge with proof-of-work or CAPTCHA alternative |
| 76+ | Deny and flag account for review |
Graduated response is what keeps false positives low. The real user hitting a new device sees an MFA prompt, not a wall, while the device fanning out across accounts hits a hard challenge.
Operational practices that hold up
Detection is not a one-time deploy. A few habits keep it effective:
- Watch the failure-rate curve, not just volume. A campaign often announces itself as a failure spike hours before takeovers succeed. Alert on the ratio.
- Prefer server-side verification. Client signals can be tampered with, so confirm the risk decision with server-side verification and treat the client result as advisory. See the general server-side vs client-side trade-off.
- Challenge with proof-of-work for suspected bots. A self-hosted proof-of-work challenge imposes real cost on bulk replay while staying invisible to humans, unlike a CAPTCHA that annoys everyone.
- Track false positives explicitly. Tie KPIs to both catch rate and false-positive rate so tuning does not silently punish real users.
- Reset exposed accounts. When a device is confirmed stuffing, force password resets on every account it successfully authenticated, not just the one you caught.
Frequently asked questions
How is credential stuffing different from brute force?
Brute force guesses many passwords against one account. Credential stuffing replays known username-password pairs from breaches across many accounts, so most attempts use the correct password and each account sees only one or two tries.
Can a WAF stop credential stuffing on its own?
Rarely. IP-based rules miss attacks spread across residential proxies, and correct-password logins look legitimate at the network layer. Device and behavioral signals are needed to catch low-and-slow replay.
What is a good success signal to alert on?
A sudden rise in login attempts paired with an unusually high failure rate, or many distinct accounts touched from one device fingerprint, are the earliest reliable indicators.
Credential stuffing succeeds because it hides inside correct passwords, so the defense cannot be about the password at all — it has to be about the device, the network, and the behavior behind each attempt. Score those together, respond in graduated steps, and reserve hard blocks for the cases that earn them. See the full account takeover prevention guide for how this fits a broader ATO program, or try the playground.
Run it yourself
Prynt is open-source, self-hostable device intelligence — visitor IDs, bot & fraud Smart Signals, and behavioral biometrics you own end to end.