There are long OWASP cheat sheets for authentication security. Most teams implement half of them, ship, and discover the gaps during a breach. Here’s the short version that catches the common mistakes.
1. Rate-limit by account, not IP
IP-based rate limits are useless against credential stuffing — every attempt comes from a different residential proxy. Limit by account: 5 failed attempts in 5 minutes triggers a 15-minute lockout, escalating to 1 hour on repeat offenders. Apply the same policy to password reset and MFA endpoints.
2. Rotate the session ID on every privilege change
Session fixation is a 20-year-old bug that still appears in audits. When a user signs in, signs up, elevates privilege (MFA, impersonation), or changes their password, generate a new session ID and invalidate the old one. Every web framework does this out of the box. Every hand-rolled auth system forgets.
3. Cookies: Secure; HttpOnly; SameSite=Lax
This is the cookie baseline. HttpOnly blocks JavaScript access (defeats XSS token theft). Secure requires HTTPS. SameSite=Lax blocks classic CSRF and is the browser default now. For cross-site contexts (embeds, etc.), use SameSite=None; Secure; Partitioned so CHIPS works correctly.
4. Hash tokens at rest
Session tokens, API keys, password reset tokens — all are credentials. If a read-only DB dump leaks, hashed tokens reveal nothing usable. Store them as SHA-256 hashes, hash on lookup, compare. Tokens have enough entropy that bcrypt-style work factors are unnecessary.
5. Verify state and PKCE on OAuth callbacks
state is the CSRF defense — a random, per-request nonce that ties the callback to the session that started the flow. PKCE is the code-interception defense. Both are required for a secure OAuth flow; neither is optional even if you “just want to ship it.” Validate both on every callback.
6. Short expirations on reset and magic-link tokens
Anything sent via email is a bearer token that might sit in a mailbox indefinitely, get forwarded, or be searched. Keep expiration windows tight:
- Password reset tokens: 30 minutes
- Magic links: 15 minutes
- MFA one-time codes: 10 minutes
And rotate on first use — a single-use token that’s already been used must be rejected on replay, even within its expiration window.
Done
These six cover most of what goes wrong. Everything else (CSP, CSRF tokens, step-up auth, device binding) is either layered on top or specific to your threat model.