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
- Using a token invalidates it.
- Requesting a new token invalidates the previous one — otherwise every old email stays live.
- Changing the email address invalidates outstanding tokens.
- 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
- Request a reset, then request another. Does the first link still work?
- Use a link twice. Does the second attempt succeed?
- Reset while logged in elsewhere. Is the other session still valid?
- Request for an address that does not exist. Does the response — or its timing — differ?
- Decode the token. Does it contain a user ID, a timestamp, or anything but randomness?