Password reset flows get built quickly and tested lightly, because the happy path, request a reset, click the email link, set a new password, works cleanly almost every time. It's also, by design, the one feature in your app whose entire job is letting someone into an account without the original password. That combination, built fast and rarely stress-tested, is worth a dedicated pass of its own.
Does the reset token actually expire
Request a reset link, then wait. Try using it an hour later, then a day later. If it still works after a reasonable window has passed, that's a token that never really goes away, sitting in an old email, a browser history, or a support ticket indefinitely, ready to be used by anyone who eventually finds it.
Does using a reset link kill any other active sessions
If an account is compromised through a leaked password, and the real owner resets it, does that new password actually kick out whoever was previously logged in with the old one? Test this directly: log in on two different browsers, reset the password from one, then check whether the other session is still active. If it is, a compromised account isn't actually re-secured by a reset, whoever already had a session just keeps using it.
Does the reset request page reveal whether an account exists
Request a password reset for an email address you know exists, and one you're sure doesn't. If the page's response is different in any visible way, faster, slower, a different message, that's an account enumeration leak: an easy way for someone to build a list of which email addresses actually have accounts on your app, useful groundwork for a lot of other attacks. Both cases should look and feel identical from the outside.
Is the reset endpoint itself rate-limited
The same math from the rate-limiting post applies directly here. An unlimited reset endpoint can be used to spam a real user's inbox with reset emails repeatedly, and depending on how tokens are generated, in rare cases contributes to other attacks if tokens aren't sufficiently random. A basic limit on how often reset emails can be requested for the same account closes this off.
Can the new password be set to anything, including the old one, or something trivially weak
Check whether your reset flow enforces the same password rules your signup does. It's an easy step to accidentally skip when reset gets built as a separate feature from signup, and the result is a backdoor around whatever password strength requirements you thought you had in place.
The five-minute version
- Confirm reset tokens actually expire within a reasonable window.
- Confirm using a reset link kills other active sessions on the account.
- Confirm the reset request page behaves identically for accounts that exist and ones that don't.
- Confirm the reset endpoint has a rate limit.
- Confirm the new password goes through the same strength check signup does.
Five checks, all doable with two browser windows and a few minutes each. The reset flow gets a fraction of the attention the login screen does, despite doing something arguably more powerful: granting access with no original password required at all.