Credential Stuffing: Why Your Login Page Is Someone Else's Business Model
How stuffing attacks are actually run in 2026 — combo lists, residential proxies, config files and solver services — and the layered defenses that work, from breached-password checks to risk scoring and the metrics that reveal an attack in progress.
Your login endpoint is under attack right now. Not metaphorically — if your product is public and has any meaningful number of users, a measurable fraction of the authentication requests hitting it today are attempts to use credentials stolen from somewhere else. For many consumer-facing services, automated login attempts outnumber genuine ones.
Credential stuffing is not clever. It requires no vulnerability in your application. It works because a large share of people reuse passwords, and billions of email/password pairs from other companies' breaches are freely available. The attacker's entire hypothesis is that some of your users used the same password somewhere that got breached.
Understanding how the attack is actually run is what makes the defenses make sense.
The economics
A stuffing campaign is a business with unit costs, and every effective defense works by making one of those costs higher.
Credentials are effectively free. Aggregated "combo lists" of billions of email/password pairs circulate openly. Cost per credential: approximately zero.
The tooling is commodity. Purpose-built credential-testing software has a plugin ecosystem — a "config" file describes how to attack a specific site: the endpoint, the request shape, how to tell success from failure, how to bypass known protections. Configs for popular targets are traded and sold. Someone else has already done the reverse engineering.
Proxies are cheap. Residential proxy networks provide millions of IP addresses belonging to real consumer connections, often sourced from users of free VPNs or SDKs bundled into mobile apps. A campaign can route each request through a different residential IP in the same city as the target user, at a cost of a few dollars per gigabyte. This is why per-IP rate limiting alone accomplishes so little.
CAPTCHAs have a market price. Solver services — some human, some machine-learning — solve challenges at rates measured in fractions of a cent. A CAPTCHA is a small tax, not a wall.
Success rates are low and profitable anyway. A campaign might see 0.1% to 2% success. Against ten million credentials, that is ten thousand to two hundred thousand working accounts, sold on for anything from a few cents to hundreds of dollars each depending on what the account contains.
Read that list and the strategy becomes clear: there is no single control that stops this. Every individual defense has a known bypass with a published price. What works is layering enough of them that the cost per successful account exceeds its resale value, and the attacker moves to an easier target.
Layer 1: make reused passwords useless
The most effective single control is also the least deployed: check passwords against known breach corpora, at signup, at password change, and — the part people skip — at login.
The k-anonymity approach makes this safe and cheap. Hash the password with SHA-1, send only the first five hex characters of the hash to the range API, and check the remainder against the returned list locally. The password never leaves your infrastructure, and neither does its full hash.
const hash = sha1(password).toUpperCase();
const prefix = hash.slice(0, 5);
const suffix = hash.slice(5);
const res = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`);
const found = (await res.text())
.split('\n')
.some((line) => line.split(':')[0] === suffix);
if (found) {
// At signup/change: reject with a clear explanation.
// At login: allow, but flag the session and require a change soon.
}
Checking at login is what closes the loop. A password that was fine when the user chose it appears in a breach corpus two years later, and that user is now stuffable. Checking on successful authentication — when you have the plaintext anyway, for the only moment you ever will — catches exactly the population an attacker is targeting.
Handle the failure gracefully: prompt at the next natural moment rather than blocking mid-session, explain why in plain language ("this password has appeared in a data breach at another site"), and do not tell the user which breach, because you do not know.
This control is powerful because it attacks the attacker's core hypothesis rather than their delivery mechanism. Proxies and solvers do not help when the credential itself is worthless.
Layer 2: rate limiting that accounts for distribution
Per-IP limits are necessary and insufficient, for the reasons above. What actually helps:
Per-account limits catch the targeted case regardless of source. Ten failures against one account from ten different countries is unmistakable.
Global and per-endpoint circuit breakers catch the distributed case. A campaign that spreads across a hundred thousand IPs still produces an anomalous total failure rate. Alert on the aggregate ratio, not on individual sources.
Limits on the whole surface, not just /login. The forgot-password endpoint, the signup endpoint (for enumeration), the OTP verify endpoint, the refresh endpoint. Attackers probe for the least-defended path, and it is almost always not the login form.
Per-ASN and per-subnet aggregation. Individual residential IPs rotate; the autonomous system numbers behind proxy providers rotate much less. Aggregating by ASN catches campaigns that per-IP limits miss entirely, and it is a small change to the key you already have.
The mechanics — algorithms, keying, and the trusted-proxy trap that silently disables all of this — are covered in protecting auth endpoints.
Layer 3: risk-based challenges
Blanket friction punishes everyone to stop a few. Risk-based authentication applies friction only where signals warrant it — and the signals are surprisingly effective in aggregate even though each is individually weak.
Useful inputs:
- Is this a device/browser we have seen for this account before?
- Is the ASN a known proxy or hosting provider? Genuine consumer logins rarely originate from a datacenter.
- Impossible travel — Berlin twenty minutes after Sydney.
- Request-shape anomalies — missing headers a real browser always sends, header ordering that does not match the claimed user agent, TLS fingerprints inconsistent with the user agent string. Real browsers are remarkably consistent; automation frequently is not.
- Behavioural timing — a form submitted 80 milliseconds after page load was not filled in by a human.
- Population-level anomalies — a spike in first-time-seen device fingerprints, or a sudden shift in the geographic distribution of attempts.
Escalate proportionally: allow silently at low risk, challenge with a CAPTCHA at medium, require email verification of the new device at high, and block outright at extreme. The key design property is that a legitimate user in the medium band can still get through — the goal is graduated cost, not a binary gate.
Two cautions. Do not build this from a single signal; each one alone has a high false-positive rate against real users on VPNs, corporate networks, or new phones. And do not let the response differ in a way that teaches the attacker which signal tripped — a distinct error message for "datacenter IP" is a free debugging tool for the campaign.
Layer 4: MFA, and being honest about it
MFA is the strongest single control against stuffing, because a correct password alone stops being sufficient. But the factor matters, and the numbers differ enormously:
- SMS OTP stops stuffing but not phishing, and is vulnerable to SIM swap. Still vastly better than nothing.
- TOTP stops stuffing, still phishable in real time.
- Passkeys / WebAuthn stop both. The credential is origin-bound, so it cannot be replayed against a phishing site, and there is no shared secret to stuff.
The realistic problem is adoption: voluntary MFA enrollment in consumer products is typically in the low single digits. Which means the honest strategy is either to make it mandatory for a defined class of accounts, or to move to passkeys where the friction is low enough that adoption is achievable.
If you make it mandatory, be careful not to undo it with a poorly-scoped "remember this device" token.
Layer 5: detect the compromises you did not prevent
Some attempts will succeed. Detection determines whether that is one compromised account or a thousand.
Watch for post-login behaviour that says "not the owner": immediately changing the email or password, adding a payment method, exporting data, generating API keys, or disabling notifications. That last one is a strong signal specifically because it is what an attacker does to hide the others.
Notify on new-device sign-in. A message to the address on file with location, device, and a one-click "this wasn't me" is the cheapest and most effective detection mechanism available, because it recruits the account owner — who has context no model has.
Monitor your success rate, not just your failure rate. This is the counterintuitive one. During a stuffing campaign, the failure rate spikes obviously. But a sophisticated low-and-slow campaign may not move it much. What does move is the ratio of successes to unique accounts attempted — a normal population of users retries their own account; a campaign touches each account once or twice and moves on. A sudden increase in "first attempt on this account, from a never-seen device, succeeded" is the highest-signal metric you can build.
The metrics worth having on a dashboard
If you take one operational thing from this article, make it these five:
- Login success rate, over time. A sustained drop means a campaign is running.
- Unique accounts attempted per hour. Normal traffic touches a stable set. Campaigns touch a wide, novel one.
- Failure rate by ASN. Sorting by this finds proxy networks in seconds.
- Share of successful logins from never-before-seen devices. The compromise indicator.
- Password-reset requests per hour. A spike is often the aftermath — either users noticing something, or the attacker consolidating.
Alert on the ratios and the rates of change, not on absolute counts. Absolute thresholds get tuned to be quiet and then stay quiet through the incident.
What not to do
Do not lock accounts on failed attempts. A per-account lockout is a denial-of-service primitive: an attacker who wants to hurt you locks out your entire user base by failing three logins against each. Throttle and challenge instead. If you must lock, lock the source, not the target.
Do not reveal which half was wrong. "Incorrect password" for an existing account and "no such user" otherwise hands the attacker a free account-validity oracle, which is the most valuable thing you can give them — it turns ten million untested credentials into a much smaller list of confirmed hits.
Do not rely on a CAPTCHA as the control. It is a layer. Priced at fractions of a cent, it is a rounding error in the campaign's budget.
Do not assume you are too small. Combo lists are tested against everything. Being small means fewer accounts, not fewer attempts.
Key takeaways
- Stuffing is a business with unit costs. Defenses work by raising a cost, and no single one is sufficient.
- Breached-password checking is the highest-leverage control — it attacks the attacker's core hypothesis. Check at signup, change, and login.
- Per-IP limits are largely defeated by residential proxies. Add per-account, per-ASN, and global circuit breakers.
- Risk-based graduated challenges beat blanket friction; combine weak signals, never rely on one.
- Passkeys end both stuffing and phishing; the obstacle is adoption, not efficacy.
- Track the success-from-new-device ratio — it is the metric that reveals a quiet campaign.
- Never lock accounts on failures, and never leak which half of the credential was wrong.
EmbedAuth applies layered per-account, per-IP, and per-endpoint limits to every hosted auth route by default, and records every attempt — successful or not — in an audit trail you can query when the graphs move.
Written by
Emilian GheoneaSenior Blockchain & Full-Stack Software Engineer. I build EmbedAuth — an embeddable authentication platform for SaaS — and write about the auth problems most teams hit too late.
Related articles
Security
Protecting Auth Endpoints: Rate Limiting and Brute-Force Defense
How to design rate limiting for login, OTP, and reset endpoints — algorithms, what to key on, lockout strategies, and avoiding the traps that let attackers through or lock out real users.
Jun 12, 2026 8 minReadSecurity
Webhook Signatures: Verifying What Your Server Is Told
A webhook endpoint is an unauthenticated API that changes your data. How to sign and verify properly — HMAC over the raw body, timestamps and replay windows, constant-time comparison, key rotation, and the idempotency and SSRF issues on both sides.
Aug 3, 2026 8 minReadSecurity
Audit Logs for Authentication: What to Record Before You Need It
The events every auth system should log, the schema that makes them queryable, why application logs are not an audit trail, tamper-evidence and retention, and how to expose logs to customers without leaking other tenants' data.
Aug 2, 2026 8 minReadSecurity
Stopping Fake Signups Without Punishing Real Users
Why bots register for your product, why CAPTCHA is the wrong first move, and a layered signup defense — honeypots, timing, disposable-domain policy, invisible attestation, progressive trust, and measuring the friction you're adding.
Aug 1, 2026 8 minRead