Account lockout after repeated failed login attempts
Why it matters
Without account lockout, a login endpoint is open to unlimited credential stuffing — automated attacks that replay leaked username/password lists from other breaches. CWE-307 (Improper Restriction of Excessive Authentication Attempts) and OWASP A07 (Identification & Authentication Failures) specifically call this out. NIST 800-53 AC-7 mandates account lockout after a defined number of consecutive failures. Distributed attacks from thousands of IPs circumvent IP-level rate limiting; only per-account lockout stops an attacker who has a valid email list and a leaked credential database.
Severity rationale
High because an unlocked login endpoint allows automated credential stuffing to compromise accounts at scale, limited only by the attacker's bandwidth.
Remediation
Track failed attempts in the database and lock the account after 5 consecutive failures within 15 minutes:
const MAX_ATTEMPTS = 5
const LOCKOUT_MS = 15 * 60 * 1000
if (user.lockedUntil && user.lockedUntil > new Date()) {
return { error: 'Account temporarily locked. Try again later.' }
}
if (!isPasswordValid) {
const count = (user.failedLoginAttempts ?? 0) + 1
await db.user.update({
where: { id: user.id },
data: {
failedLoginAttempts: count,
lockedUntil: count >= MAX_ATTEMPTS ? new Date(Date.now() + LOCKOUT_MS) : null,
},
})
}
Reset failedLoginAttempts to 0 on successful login. Store the counter in the database, not in-memory — in-memory counters reset on deployment.
DetectionPro
The detection method for this pattern is part of AuditBuffet Pro.
The full method (what to look for, pass and fail conditions, and how results get reported) ships inside the Security Hardening Audit. The pattern definition, severity rationale, and remediation remain openly licensed under CC-BY-4.0.
External references
- cwe · CWE-307 — Improper Restriction of Excessive Authentication Attempts
- owasp:2021 · A07 — Identification and Authentication Failures
- nist:rev5 · AC-7 — Unsuccessful Logon Attempts
Taxons
History
- 2026-04-18·v1.0.0·Initial import from security-hardening·automated