Authentication Vulnerabilities: The Complete Practical Guide
Securing Authentication
How to secure a login for real: generic errors, server-side rate limiting, phishing-resistant MFA, safe reset tokens, session rotation, and stuffing defenses.
Published
Every attack in this cluster comes back to the same root: the server accepted a proof of identity it should have rejected, usually because it trusted something the client controlled. Securing authentication is therefore mostly about moving the trust anchor to the server and removing the oracles that tell an attacker they are getting warm. This page collects the defenses that close the whole cluster. Build the habit on a free live target.
Give nothing away: generic responses
Username enumeration dies when every failure looks identical. Return one generic message for a failed login, a reset request, and a signup, regardless of whether the account exists, and keep the status code, redirect, and response length the same across both cases. Equalize timing too, by performing the same password-hash work whether or not the account was found, so a missing user is not faster than a real one. The goal is that a failed attempt reveals only that it failed, never why.
Rate-limit on something you own
A throttle is only as trustworthy as the signal it keys on. Rate-limit server-side on the account and on a source signal you actually control, never solely on a client-supplied header like X-Forwarded-For, and only trust a forwarding header when it comes from infrastructure you operate. Normalize the username before counting so casing and whitespace variants share one bucket. Prefer exponential backoff to a hard lockout, because a lockout that anyone can trigger becomes a denial-of-service weapon against real users. Add spray and stuffing detection: many accounts hit with one password, or many attempts from one source, are patterns worth alerting on.
Enforce MFA where it counts, with strong factors
A second factor only helps when it is enforced on the server at the final sensitive step, bound to the same session and account as the first factor, never a UI-only gate and never a client-supplied verified flag. Prefer phishing-resistant factors (WebAuthn passkeys, hardware keys) over SMS and email codes, because they bind authentication to the real origin and cannot be replayed to a phishing site. When you do use one-time codes, make them single-use, short-lived, strictly rate-limited, and invalidated after a few wrong attempts, and hold recovery and backup flows to the same standard as the primary factor.
Make reset tokens unforgeable and unredirectable
The reset flow mints a credential, so treat its token like one. Generate high-entropy, single-use, short-TTL tokens, store only their hash, and bind each token to exactly one account server-side, deriving the account from the token rather than from any client-supplied field. Never place a token in a response body, header, or log. Build the reset URL from a server-side configured host, never from the Host or any forwarding header, which is what kills reset poisoning. Invalidate all reset tokens and active sessions when the password changes, and re-authenticate before an email or password change, confirming email changes out-of-band.
Own the session and persistent state
Issue a brand-new session id at login and on every privilege change, so a planted id cannot be fixed in advance. Set session cookies HttpOnly and Secure and never accept a session id from a URL. Sign any persistent "remember me" state with a strong, random server secret, make it revocable, tie it to server-side state rather than trusting the cookie alone, and invalidate it on logout and password change. The rule underneath all of this: nothing the client holds is trusted on its own.
Defend the credentials themselves
Beyond the flows, reduce the odds the credential is guessable. Check passwords against known-breach corpora at signup and reset so reused and common passwords are rejected, encourage or require a password manager and length over complexity theatre, and layer in device and behaviour signals to spot stuffing. Strong, unique credentials plus the controls above mean that even a correct password alone rarely gets an attacker in.
The short version
- Identical generic responses everywhere, with equalized timing, so nothing leaks account existence.
- Server-side rate limiting on the account and a trustworthy signal, with backoff instead of weaponizable lockout.
- MFA enforced server-side with phishing-resistant factors, and single-use rate-limited codes.
- Reset tokens that are high-entropy, single-use, expiring, user-bound, and never in a response, with the link host set server-side.
- Session id rotated at login, signed and revocable persistent state, real server-side logout.
- Breach-password checks and anomaly detection on top.
For the offensive side in one place, see the authentication cheat sheet, and to prove to yourself that these controls close the bugs, break a login that is missing them on a free live target.
← Back to Authentication Vulnerabilities: The Complete Practical Guide