Credential stuffing

Attackers don't guess passwords — they replay millions of real ones leaked from somewhere else.

The idea

People reuse passwords. When one site leaks its password database, that list of email:password pairs gets traded and replayed against every other site. The attacker isn't brute-forcing one account — they're trying known-good credentials against your login, betting that some fraction of your users reused theirs.

Credential stuffing is high-volume, low-per-account, and uses valid passwords, so it sails past naive "wrong password" defenses. The hits convert directly into account takeover. Defense is about raising the cost per attempt — rate limits, bot detection, and a second factor the attacker can't replay.

Press play to watch a wave of replayed logins hit the login endpoint.

How it works

The failure mode: a login endpoint that treats every request the same, with no per-IP / per-account throttle and only a password as proof. Valid leaked credentials slip through and the only signal is a quiet spike in successful logins from new devices.

The fix is layered. Throttle attempts, fingerprint bots, and require a factor the attacker doesn't have. Below, a successful password check is necessary but not sufficient — a step-up factor gates the session.

def login(email, password, ctx):
    # 1. Throttle BEFORE doing expensive work — per IP and per account.
    if rate_limiter.too_many(ctx.ip) or rate_limiter.too_many(email):
        raise TooManyAttempts()          # 429, with backoff

    # 2. Constant-time check; never reveal "user exists" vs "wrong password".
    user = users.get(email)
    if not user or not verify_password(password, user.hash):
        audit("login.failed", email, ctx.ip)
        raise InvalidCredentials()       # same message either way

    # 3. Password is necessary, not sufficient. Step up when risk is high.
    if risk_engine.is_suspicious(ctx) or user.mfa_enabled:
        return require_second_factor(user)   # TOTP / passkey / push

    return issue_session(user)

Signals

SignalWhat credential stuffing looks like
Login volumeSharp spike in total attempts from many IPs
Failure rateVery high failure ratio (most pairs don't match)
Success patternSuccesses from new devices / new geographies
User agentHeadless / scripted clients, rotating proxies
Account spreadOne attempt each across thousands of accounts

Watch out for

Worked example

An attacker buys a list of 2,000,000 email:password pairs leaked from an unrelated forum and replays them against your login. Suppose 0.5% of your users reused those exact passwords. With no throttle, that's ~10,000 instant account takeovers in minutes.

Add a per-account limit of 5 attempts per hour and the attacker still gets one shot per account — so add bot detection on the IP spike, and require MFA on logins from unrecognized devices. Now a correct password from a new device lands on a second-factor wall the attacker can't clear, and the 10,000 takeovers collapse toward zero while the spike trips your alerts.

Check yourself

Your login spike is mostly failed attempts, but a handful succeed from brand-new devices. What's the highest-leverage fix?

Coach note: a correct password is the attacker's starting point here, not their obstacle — so the leverage is in the layer after the password, not the password itself.