Defending the Login
Your login endpoint is one of the most attacked URLs you own. Credential abuse was the top initial access vector in the 2025 Verizon Data Breach Investigations Report, at 22% of breaches. In the 2026 report it fell to 13%, overtaken by exploited vulnerabilities at 31%. (Verizon DBIR) Read that as attackers finding easier doors, not as login getting safe: stuffing, phishing, and recovery abuse have not stopped. Defend the login in layers, and assume each layer will sometimes fail.
Threat model in one table
| Threat | What the attacker does | Primary defenses |
|---|---|---|
| Credential stuffing | Replays leaked email and password pairs at scale | Breached-password checks, risk scoring, bot defense, passkeys |
| Password spraying | Tries a few common passwords across many accounts | Blocklist of common passwords, per-IP and global anomaly limits |
| Phishing and real-time relay | Proxies your login page and relays codes | Passkeys (origin-bound), notifications, risk on new devices |
| MFA fatigue | Floods push approvals | Number matching, push rate limits, alert on denied pushes |
| Account enumeration | Learns which emails have accounts | Identical responses and timing on login, signup, and reset |
| Session theft | Steals cookies or tokens via malware or XSS | HttpOnly cookies, short tokens, DPoP, device binding, session list |
| Recovery abuse | Targets reset and support flows | Chapter 12 layers, waiting periods, notifications |
| SIM swap | Takes over the phone number | SMS as fallback only, never sole factor for recovery |
| Denial via lockout | Locks real users out on purpose | Progressive delays instead of hard lockouts |
Rate limiting that does not lock out real users
Apply limits on several keys at once, with different windows:
| Key | Example policy |
|---|---|
| Per account | Progressive delay after 5 failures; risk challenge after 10; ceiling well under NIST's 100 |
| Per IP or subnet | Burst limit per minute; stricter for hosting providers and anonymizers |
| Per device fingerprint | Burst limit per minute |
| Global | Alert when failure rate across all accounts jumps; this is how you spot stuffing |
| Per destination | Limit codes and links sent to any one email or phone |
Use a sliding-window counter in your session cache, keyed by hashed identifiers. Return the same generic error to the client whatever limit was hit.
Risk scoring
Score every sign-in on device familiarity, network reputation, geovelocity, credential breach status, and behavior. Map scores to actions: allow, step-up with a phishing-resistant factor, or block and notify. Log every score and its inputs so you can tune thresholds against real fraud.
Enumeration resistance
- Same message for "no such account" and "wrong password."
- Same response for reset requests whether or not the account exists.
- Equal timing: run a dummy hash verification when the account does not exist so response time does not leak it.
- Signup: handle existing accounts by email, not on-screen (Chapter 12).
Web security basics on auth pages
- A strict Content Security Policy with no inline scripts on login pages.
frame-ancestors 'none'to block clickjacking.- CSRF tokens on every state-changing form, in addition to
SameSitecookies. - No third-party scripts (analytics, chat widgets, tag managers) on authentication pages. Each one is a script that can read what users type.
- HSTS with preload on your auth domain.
Notifications
Email the user on new device sign-in, password change, factor added or removed, email change, and recovery started. Each notification includes a "this was not me" link that freezes the account and starts recovery. Users become your detection system.
Common mistakes
- Hard lockouts after 3 to 5 failures, which attackers use to lock out real users.
- Rate limits only per IP, which botnets spread across thousands of addresses.
- Analytics or marketing scripts on the login page.
Key takeaways
- Defend in layers: breach checks, risk scoring, bot defense, and passkeys, each assumed to fail sometimes.
- Rate-limit per account, IP, device, destination, and globally; global failure spikes reveal stuffing.
- Make responses and timing identical for existing and non-existing accounts.
- Keep third-party scripts off authentication pages entirely.