27

I am new to web development and am trying to implement a password reset feature according to the OWASP cheat sheets: Forgot Password Cheat Sheet

The cheat sheet advises not to send the username as a parameter when the form is submitted and sent to the server. Instead one should store it in the server-side session.

However, I am not sure how I should do that, since for me to be able to store the username in such a way, the user needs to enter his/her username and send it to the server at some point, right? Why not send it together with the form where the user answers security questions? Or am I just understanding this the wrong way?

2
  • 3
    Maybe I'm going blind, but I can't find the recommendation not to include the username as a parameter in the page you linked. Could you quote the sentence you are referring to?
    – James_pic
    Commented Jun 22, 2020 at 10:04
  • So just to point out the obvious - You dont let the user give the username along with the password change / reset request to prevent users from taking over accounts they learned the email-address by accident (or intelligent guesses) - that way they can just reset "THEIR" account, not somebody elses ... Depending how complex your check is - guess you sent password reset request for an existing username (you learned by using your system) and your email-address - both exist and bam .. you captured a different user's account
    – eagle275
    Commented Jun 23, 2020 at 9:00

7 Answers 7

51

This is what I usually do:

  1. The user asks for a password reset.

  2. The system asks for the registered email.

  3. The user enters email, and no matter if email exists or not, you say that you sent a reset link.

  4. The server stores email, expiration and reset token on a reset_password table

  5. When the link is accessed, expiration is checked and a form to reset the password is shown.

User only receives a link with a large random token.

13
  • 31
    It's because an user may change the username on the request and create a reset link for another user.
    – ThoriumBR
    Commented Jun 21, 2020 at 18:18
  • 14
    There's a lot of debate (obviously) around point 3.. An attacker can just try to register with the given e-mail and check if such account exists. I would just keep it generic and say "user enters e-mail", nothing more, nothing less.
    – Gizmo
    Commented Jun 22, 2020 at 6:28
  • 4
    A good usability enhancement to 3 without compromising security is to send a You don't appear to have an account ... Sign up here (link) email if there is no account for the entered email (but still give the same "sent a reset password email" message on the reset password request form). Commented Jun 22, 2020 at 9:18
  • 25
    @dmuensterer I wouldn't call it a "large" attack vector. Often times you can find out if a given email is registered or not simply by trying to register with the email: you won't be able to register if the email is already registered. Therefore, ambiguous responses to password resets are not a slam-dunk way of hiding information, but they can cause a lot of confusion to end users. I personally include this step in my own systems, but it's not crazy for systems that choose not to. Commented Jun 22, 2020 at 9:45
  • 11
    3 is a bit of nightmare as I usually resort to resets on sites that I've no idea which of the 7 email I registered with 10 years ago.
    – Džuris
    Commented Jun 22, 2020 at 10:50
16

You should not trust the client. So if the client is able to send their username during the stage where the new password is entered, what happens if they change the username to someone else's? They will be able to reset the account of another user and take over the account.

@ThoriumBR's answer is of course correct. Another option is to store no state on the server, and encode all information in the reset link, such as username and expiry time. This can be done securely by cryptographically signing the information, using HMAC or similar. There are likely libraries for your language that can handle this, such as itsdangerous for Python.

3
  • the problem with taking over another user account only exists if you let the user choose both, username and password. If you go with only one, either email or username (and send the email then to the user's registered email) that is avoided either way, no? Commented Jun 22, 2020 at 21:26
  • @FrankHopkins I'm talking about after the reset link has been received Commented Jun 22, 2020 at 21:42
  • ah, sure, that makes sense. I wasn't sure which step OP meant with their question in general. But as an improvement suggestion for the answer then: maybe point out that you mean the same instant in the process where they send their new password in the same request. Commented Jun 22, 2020 at 22:01
5

To add to ThoriumBR's excellent response:

  • user asks for a password reset
  • system asks for the registered email
  • user enters email, and no matter if email exists or not, you say that you sent a reset link but does not reset the password yet
  • server stores email, expiration and reset token on a reset_password table
  • when the link is accessed…
    • server checks expiration of reset token,
    • checks it has not been used before,
    • requires user to enter additional authentication tokens, could be 2FA, SMS code, answer to preset security questions, etc, and finally
    • opens a form to reset the password,
    • after which the user should be left in a "logged out" state, whereby they would need to login with the new password to access the application.
  • additionally, the server validates that the reset link was used only once within the expiration time

User only receives a link with a large random token.

2
  • 1
    Shouldn't you also log out other existing sessions? Commented Jun 23, 2020 at 14:45
  • yes, that would be wise, after the user has authenticated with alternative auth factors.
    – Pedro
    Commented Jun 23, 2020 at 14:49
3

Sending user name has no sense. On the server side you store information which password reset token is related to which user. When user clicks on the link, you know exactly what user it is. You obtain user name based on the token and display it in the password reset form.

1

To reset a password is an authenticated operation and requires that the user has identified themselves in a secure manner. Usually it is done with a user name and password plus a second factor. The user name and password are often submitted at the same time in the same HTTP request. This is not uncommon and is not a red flag.

In a forgotten password scenario, the need to authenticate the user is exactly the same. This means they are submitting their user name plus some alternative form of credentials (it sounds like, in your case, you are using challenge questions, which I shall not comment on). Other than that, it's still authentication, and it poses no problem to submit the user name and the credentials at the same time, just like with regular signon.

The problem arises when you collect the user name on a separate page. This is common for workflows involving a user-specific challenge and response (such as challenge questions but also with out-of-band OTPs) because the system needs to know the user name ahead of time so it knows what questions to display or where to send the OTP. The temptation is to collect the user name on a separate page, then pass it around as a hidden form variable. This is subject to tampering on the client side and therefore provides a vector for an IDOR attack.

The mitigation is to handle the forgotten password "session" the same way that you manage an ordinary signon session; maintain the user name in a tamper-proof fashion, such as an encrypted cookie or a session variable. Hidden fields are trivial to tamper with.

0

I've done similar to what is described in ThoriumBR's excellent response, but without using a temporary reset password table, like so:

  • The user asks for a password reset.

  • The system asks for the registered email.

  • The user enters email, and no matter if email exists or not, you say that you sent reset link. The link is constructed by encrypting the username and an expiration time using AES with a key known only to the server.

  • When the link is accessed, the link is decrypted using the key known to the server, producing the username and the expiration. If the decryption is successful, then the expiration is checked and if the expiration time has not passed, a form to reset the password is shown. The encrypted link is populated in a hidden field in the form. When the form is submitted, the link is again decrypted using the key known to the server, producing the username and the expiration. If the decryption is successful, then again the expiration is checked and if the expiration time has not passed, then (and only then) is the password reset for the user.

User only receives a link with a large random token.

0

I have a highly upvoted answer on this very question already. You can build on that to include other features like entering emails instead of usernames and security questions.

  1. The user enters his username and hits "forgot password". I also recommend the option of entering the email address instead of the username, because usernames are sometimes forgotten too.
  2. The system has a table password_change_requests with the columns ID, Time and UserID. When the new user presses the button, a record is created in the table. The Time column contains the time when the user pressed the "Forgot Password" button. The ID is a string. A long random string is created (say, a GUID) and then hashed like a password (which is a separate topic in and of itself). This hash is then used as the 'ID' in the table.
  3. The system sends an email to the user which contains a link in it. The link also contains the original ID string (before the hashing). The link will be something like this: http://www.mysite.com/forgotpassword.jsp?ID=01234567890ABCDEF. The forgotpassword.jsp page should be able to retrieve the ID parameter. Sorry, I don't know Java, so I can't be more specific.
  4. When the user clicks the link in the email, he is moved to your page. The page retrieves the ID from the URL, hashes it again, and checks against the table. If such a record is there and is no more than, say, 24 hours old, the user is presented with the prompt to enter a new password.
  5. The user enters a new password, hits OK and everyone lives happily ever after... until next time!

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .