This post is part of the series 'Password management'. Be sure to check out the rest of the blog posts of the series!
Users forget their passwords from time to time. This article explains how to safely restore their access to your application. This post covers what to do when a user clicks the submit button on a page like this:
Password Lost page sample
#❌ Resend password by email
If you are considering this solution, you are not storing passwords in the right way. Indeed, you should not be able to do that. I strongly advise you to read my previous post about storing password and come back here later.
#❌ Questions / answers
You have probably seen websites asking you to enter a question and its associated answer when registering. This approach is problematic because the questions are often not personal enough and anyone can guess the answer. Sometimes, simply visiting someone's Facebook profile reveals it. You can read about how Sarah Palin's Yahoo account was hacked as a real-world example. In the end, the answer to the security question needs to be treated like a password to be safe, which defeats the purpose.
In short, this solution is impractical and unreliable.
#❌ Create a new password and send it to the user by email
This solution replaces the current password with a new random password.
Problem 1: There is no guarantee that the person requesting the reset is the actual account owner. The reset form only asks for a username or email address, so anyone can submit a request for someone else. This makes it trivially easy to lock a user out by repeatedly resetting their password. One workaround is to keep both passwords valid simultaneously, but that makes brute-forcing the account twice as easy.
Problem 2: The new password is visible in the mailbox. Some people do not protect their computers or phones with a password, so anyone with physical access to the device can read the new password and log in. This can be countered by requiring the user to change their password on first login with the new one. The password in the email then becomes invalid after the reset (provided you enforce a policy preventing reuse of old passwords). Additionally, the generated password should expire quickly. When you reset your password, you are expected to act immediately, so the generated password should be valid for no more than 10 minutes. This limits the exposure window to anyone who can read your email within that timeframe.
#✓ Create a password reset token
By combining the ideas above, we can create a solution that generates a one-time token usable on the reset password page. The website sends a link similar to https://example.com/PasswordLost?token=467dc4ad9acf4 by email. When the user navigates to the reset page, the site verifies that the token is valid and allows the user to change their password.
This solution is similar to the previous one in that a token is generated and sent by email. The key difference is that this token does not replace the current password, does not equal the new password, and is long enough to resist brute-force attacks. After the user submits this token on the dedicated page, they are required to set a new password. For added security, the token should have a short validity window: about ten minutes is sufficient. The token must also be single-use.
There are many ways to generate the token. You can generate a random string, store it in a database with the associated email address and expiration date, and validate it by querying the database. Another approach, which I prefer, is to generate a server-encrypted token. You then decrypt it and validate the data it contains (user email, expiration date, and last password change date). This way you do not have to store anything on the server. This is what ASP.NET Core Identity does.
You now have a reset URL, but what should you include in the email you send?
- Relevant and readable subject and "From" name
- The reset URL
- Expiration information for the link (10 minutes? 1 hour?)
- Who requested the reset? (IP address? Operating system? Browser name?)
- Support contact information
You can check the template made by Postmark: Password reset template
Example of a Password Lost email template
#⚠ Be careful when creating the URL
When you send a reset email with a link to the password reset page, you need to construct the absolute URL (the combination of domain and relative path). It is common to use the domain from the incoming request to build the full URL, for instance via HttpContext.Request.Host. This is convenient because you do not have to hardcode the host in a configuration file. However, if your server is not properly configured, it may accept requests with any Host header value, not just the domains you own. A malicious user can then submit a reset request for someone else's email address while setting the Host header to a website they control. If the victim clicks the link in the email, they are redirected to the attacker's website instead of yours. This enables a phishing attack.
The malicious user just has to set a custom Host header when sending the request to your website, so there are no complicated things here. You'll find the documentation of the Host header on MDN.
✓ Be sure to configure your server to only listen on the domains you own, so you know the Host header has a valid value
✓ Don't use the value of the Host header, directly or using a property of the framework you use, to compute the full URL. Instead, use a value from a configuration file or hardcode the value.
✓ Never trust user inputs, whatever the input is! And, in this case, it's an HTTP header. Be sure to always validate and sanitize the user inputs.
#⚠ Do not disclose the existence of the account
When a user enters their email address on the reset password page, you should not indicate whether an account associated with that address exists. Users do not want anyone to be able to determine whether they have an account on a particular website. Confidentiality matters more on some websites than others. Imagine a spouse discovering that you use a dating service. Instead, always show a generic message:

You can also send an email to inform the recipient that no account was found for their request. Let them know the request was made and whether they should be concerned. If they did submit the request and their email address was not found, suggest they try a different one. You can use this template for this type of email.
Do you have a question or a suggestion about this post? Contact me!