Web security

The password reset flow is your real authentication system

In one paragraph

A secure password reset needs cryptographically random single-use tokens, short expiry, invalidation on use and on any new request, no user enumeration in responses or timing, session revocation across all devices after a reset, and rate limiting on both the request and the confirmation endpoints.

Teams spend weeks on login: password policy, MFA, lockout, session handling. Then password reset gets written in an afternoon by whoever picked up the ticket, and never looked at again — despite being a fully functional path to issuing credentials for any account.

It is where I start on any web assessment, and it produces findings more often than the login flow ever does.

Token generation

  • Use a cryptographically secure random source, at least 128 bits of entropy. A token derived from a timestamp, a user ID or an incrementing sequence is guessable, and someone will guess it.
  • Store a hash of the token, not the token itself. A database read should not hand over working reset links for every pending request.
  • Single use. Mark it consumed inside the same transaction that changes the password, or a race lets it be used twice.
  • Short expiry: fifteen to sixty minutes. A day-long window is a day of exposure for every link sitting in an inbox.

Invalidation rules people forget

  1. Using a token invalidates it.
  2. Requesting a new token invalidates the previous one — otherwise every old email stays live.
  3. Changing the email address invalidates outstanding tokens.
  4. Completing a reset revokes every existing session and refresh token, on every device. This is the step most often missing, and it means an attacker who already has a session keeps it after the legitimate owner "recovers" the account.

Do not leak who has an account

Enumeration through the reset flow is a small finding on its own and a useful one in combination — it turns a leaked password list into a targeted campaign.

  • Return the same response whether or not the address exists: "if that address has an account, a link is on its way".
  • Keep the timing constant. Sending mail only for real accounts creates a measurable difference; queue the work either way.
  • Do not reveal existence through validation errors, redirect targets or rate-limit messages.

Rate limit both ends

  • The request endpoint, per address and per IP — otherwise it is a mail bomb aimed at your users and your sender reputation.
  • The confirmation endpoint, per token and per IP, because an unlimited token endpoint is a brute-force target regardless of entropy.
  • Alert on volume. A spike in reset requests is one of the clearest early signals of credential stuffing.

The email itself

  • Link over a code where possible, and never both interchangeably.
  • No sensitive data in the message, and no reflection of user-supplied content that could be used to inject content into the mail.
  • Say what was requested, when, and what to do if it was not them — that is your out-of-band alert as much as it is a courtesy.
  • Send a second, separate notification when the password actually changes.

A five-minute test

  1. Request a reset, then request another. Does the first link still work?
  2. Use a link twice. Does the second attempt succeed?
  3. Reset while logged in elsewhere. Is the other session still valid?
  4. Request for an address that does not exist. Does the response — or its timing — differ?
  5. Decode the token. Does it contain a user ID, a timestamp, or anything but randomness?

Want this tested properly?

Scope and a fixed quote within two working days. First consultation is free.