In this type of situation, implementing a secure forgot password feature is challenging. Sending a password reset link via email is probably the best option (barring a non-automated solution where users call customer support). So here I will offer up some specific ideas on how to secure the process when using email.
- When a user invokes the forgot password process, don't say anything about whether the username entered was recognized or not. It should simply display a generic message such as: "Thank you. If the username you provided is valid, we will send you an email with instructions on how to reset your password".
- Along with the above, don't show the email address where the email was sent. It might give legitimate users a warm, fuzzy feeling but it definitely helps attackers in a number of scenarios.
- The password reset link in the email message should incorporate a GUID or similar high-entropy token. The token could be a parameter in the query string or part of the URL path itself. It doesn't really matter.
- Allow only one valid token per user at any given time.
- Make sure the email message does not include the username.
- Make sure the link can be used only once. In other words, invalidate the token immediately when an HTTP request containing that token is received.
- The link should expire. Depending on your situation, implement logic to invalidate the token 10, 20, or 30 minutes after the email is sent out. Make it a configurable value so it can be adjusted if needed without a code change.
- The password reset page (the one that appears after clicking the link) should force the user to re-enter his username.
- If the username entered is incorrect 3 times in a row, lock the account. Remember, your application knows which username is associated with the token. The person attempting to reset the password should know it as well.
- After a successful password reset, send a confirmation email to the user to notify them it happened. This can alert users to fraud if they didn't initiate it.
- Throughout each step of the process, make sure the application is logging everything that occurs so there's a solid audit trail in case something goes haywire.
(updated on May 5, 2014 based on some feedback I received)