Account security

Credential stuffing defense: what login operators should do

6 min read

Key points

  • Credential stuffing replays username and password pairs leaked from other services, exploiting password reuse.
  • The defense is layered, with MFA at the center plus breached-password checks, rate limits, bot defenses, and user alerts.
  • Passkeys have no reusable secret, so closing the password path for users who moved to passkeys shrinks what attackers can target.

Credential stuffing is an attack that takes username and password pairs leaked from other services and tries them against your login. If users reuse passwords, attackers get in even though nothing leaked from you. The defense is layered: multi-factor authentication at the center, plus checks against breached passwords, rate limits, bot defenses, and alerts to users. The lasting fix is to move users to a method with no reusable secret, such as passkeys, and narrow the password path over time.

What credential stuffing is

OWASP defines credential stuffing as testing username and password pairs obtained from breaches of other sites against accounts. MITRE ATT&CK describes it as adversaries using credentials from breach dumps of unrelated accounts to access target accounts through credential overlap.

Comparing it with related attacks shows what makes it different:

Attack What is tried Attempts per account
Brute force Many passwords against one account Many
Password spraying A few common passwords against many accounts Few
Credential stuffing Leaked username and password pairs Few (often one)

Because credential stuffing often tries each account only once, a per-account rule like "lock after five failures" rarely triggers. You need other signals.

Spotting it

MITRE ATT&CK suggests watching for many failed sign-ins with distinct username and password pairs from a single IP address in a short time. In your own logs, look for:

  • Sign-in volume and failure rates that break from the usual pattern for that time of day
  • A rise in attempts against usernames that don't exist
  • Many different accounts tried from the same IP address or network
  • Successful sign-ins followed immediately by changes to the email address, password, or profile

The last one matters most. Once in, attackers try to lock the real user out by changing contact details and authentication methods.

Layered defenses

1. Multi-factor authentication

The OWASP Credential Stuffing Prevention Cheat Sheet puts MFA first, as by far the best defense against most password-related attacks. A correct password is not enough without the second factor.

MFA methods are not equally strong, though. SMS and app one-time codes can be relayed if a user is tricked into typing them on a fake site. If phishing is in scope, choose a phishing-resistant method such as passkeys. See what phishing-resistant MFA means.

2. Check against breached passwords

NIST SP 800-63B-4 requires comparing new passwords against a blocklist of commonly used, expected, or compromised values when they are set or changed. Refusing passwords that are already leaked shrinks the pool of pairs that will work. The same document says not to impose composition rules (mixing symbols or uppercase) or periodic forced changes.

3. Rate limits

Limit by account and by source (IP address and similar). NIST SP 800-63B-4 requires limiting consecutive failed attempts on a single account to no more than 100. The OWASP Authentication Cheat Sheet warns that lockouts can be abused to lock other people out, and suggests keeping a recovery path open while an account is locked.

Since credential stuffing makes few attempts per account, per-source limits and monitoring of overall failure rates do most of the work.

4. Bot defenses

For cases where MFA isn't possible, OWASP lists CAPTCHA, IP reputation, device and connection fingerprinting, requiring JavaScript, and multi-step login, and notes that none of these is as effective as MFA on its own, so they should be layered.

5. Don't reveal which usernames exist

On failure, return one message such as "Invalid username or password" that doesn't say which part was wrong. OWASP notes that differences in wording or response time can let attackers enumerate valid usernames.

6. Tell users

Notify users about sign-ins from new devices, password or email changes, and repeated MFA failures. OWASP also recommends alerting users to unusual security events. A user who notices and reports early limits the damage.

Shrink the target with passkeys

Everything above is defense that never ends as long as you accept passwords. Passkeys lack the thing this attack depends on:

  • A separate key is created for each service, so users can't reuse it.
  • The server holds only a public key, so a leak elsewhere gives attackers nothing that works on your login.

But if a user with a passkey can still sign in with a password, attackers will simply try the password. To get the benefit:

  1. Close the password path for users who have a passkey. Set a date and phase it in: stop showing the password field to those users, then stop accepting their passwords.
  2. Guard passkey enrollment. Require your existing MFA or re-authentication before registering a passkey, so an attacker who got in with a stolen password can't add their own.

First steps when you're under attack

  1. Tighten per-source limits and, if needed, temporarily strengthen bot defenses.
  2. List successful sign-ins during the attack window and invalidate those sessions.
  3. Have the affected users reset their passwords, and check whether contact details were changed.
  4. Advise users to change the password anywhere else they reused it.

Implementing with SealGate

SealGate has no passwords and never sees password sign-in attempts. Password-side defenses (breached-password checks, rate limits, bot defenses) stay in your own login. SealGate helps with the move to passkeys:

  • Set a "Password login end date" in the environment settings in the dashboard. After that date, login responses (POST /v1/login/verify) for users who registered a passkey return password_allowed: false, which you can use to hide the password field.
  • Adding a passkey for a user who already has one requires signed_in: true or a recovery_token on /v1/register/options. Your server sets signed_in only after confirming the request comes from the signed-in user.
  • For a user you suspect was taken over, POST /v1/users/{external_id}/revoke revokes all of their passkeys.
  • Passkey registration and login outcomes are in the audit log (with IP and User-Agent) and in webhooks such as login.failed.

See the docs for details.

FAQ

Can we be targeted if nothing leaked from us?

Yes. Credential stuffing uses pairs leaked from other services. If a user reused the password, it works on your login too.

Is account lockout enough?

No. The attack makes few attempts per account, so per-account lockout rarely fires. Combine it with per-source limits and MFA.

If we add passkeys, can we drop password defenses?

Not while password sign-in remains. The scope you need to defend shrinks as you close the password path for users who have passkeys.

Sources

Related posts

Back to the blog