Access and accounts Required
Make password resets safe
Send a long random reset link that works once, expires within an hour, and says the same thing whether the account exists or not.
How to do it
Make the token with a secure random source and store only its hash. Build the link from your own configured address, never from the request's Host header. End all other sessions after a reset. Make recovery as strong as login. A reset must still ask for the second factor, or use a recovery code.
How to check
Ask for a reset for a real and a made-up email. The replies are the same. Use one link twice, and send a reset request with Host: aw-test.example.org. The second use fails, and the email still links to your own domain. On an account with two-factor login, the reset still asks for the second factor.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers security testing playbook: https://agentwranglers.se/playbook/adversarial-testing/access-and-accounts/password-reset.