12
votes

Users forget passwords, and (nearly) all membership sites need a way to help users get back in.

I'd like to implement the common scenario:

  1. User hits site, tries to log in, can't, and realizes they forgot password - crap!
  2. User enters email address and clicks "forgot password"
  3. User gets email with a password reset link

Here's how I'm planning to implement this (C#/ASP.NET MVC):

  1. When the user enters email and hits "forgot password" button my site will generate a GUID, store it on the member's entity in the DB (member.ResetToken), and email them a link with that GUID in the url (the email sent will inform them they can use this link to one time only)
  2. User clicks the link and my site looks up their account based on that member.ResetToken from the url. If their account is found show them a password reset form, and when they complete the reset it clears the member.ResetToken from their account.

Here's my question: keep it like this (in which they can reset their password with that link at any time, now or in the future) or add a timestamp to limit how long they have to reset their password?

From a UX perspective the ability to reset your password whenever you're ready is great, but I want to make sure I'm not overlooking some security issues this could raise.

4
Generate a reset request and email it out and make it time limited, but don't change the password. By changing password and sending out reset, it's easy to blow out someones password :)Darren Kopp
That's not what I described. ;) The password is changed by the user. A url with password reset token is mailed to them when they click the "forgot password" button. The GUID is the password reset token (though I'll probably not use a GUID, but something more random). I'm asking if a one time use token is sufficiently secure versus using a time limited token. Thanks though.Chaddeus
Yeah, I understood what you were saying, check out my answer.Darren Kopp

4 Answers

10
votes

Your scheme actually works, but there are some points that could be improved. But first to your original question about the time limit:

Let's ask the opposite question: Why should a token remain valid infinit?

There is no advantage, when the reset-link can be clicked two years later, either the user clicks the link in about an hour or he has probably forgotten about the link (and can request a new one if necessary). On the other hand, being able to read the e-mails doesn't necessarily mean, that an attacker must hack the e-mail account, there is for example the open e-mail client in the office, a lost mobile phone, a backup on the (lost) USB drive...

The most important improvement is, that you should only store a hash of the token in your database. Somebody with access to the database (SQL-injection), could otherwise demand a password reset for any e-mail address he likes, and because he can see the new token, he could use it to set his own password.

Then i would store those reset informations in a separate table. There you can store the userid, the hashed token, an expiry date and the information whether the link was already used. The user is not in a special state then.

Maybe i misunderstood this point, but the reset link should point to a special page for password resets. When the user goes to the login page, there should be no special handling, the login page should not be aware that there is a pending password-reset.

The reset token should be unpredictable, this can be achieved best with a really random code, reading from the random source of the operating system.

2
votes

So, there are a few problems with this approach that I was trying to elude to in my comment. When you store the "confirmation token" in the users password, you have just basically destroyed their password.

I, a malicious person, can then take a big giant list of email addresses and a bot net and flood your server with password reset requests and lock out your users from their account. Sure, your users will get an email for the reset, but if I can reset passwords fast enough, there may be a backlog of emails (or, if you do it synchronously, i can likely DoS the entire application).

I, a normal user of your system, may attempt to reset my password, and can't figure out why I'm not getting the reset email because I don't even know about a spam folder (or it never arrived). Fortunately, I just remembered what the password was, but it doesn't work anymore since the password is now an opaque GUID, and I'm basically dead in the water until I can find the reset email.

Here's the process you should use.

  1. Generate a password reset request which you look up using a GUID, and you could likely also secure this by hashing that value with some private data and passing that in the URL as well to avoid a rapid attack. You can also lock this request down by making it only valid for a certain amount of time.
  2. Once someone follows that link with a valid token and any other parameters you specify, they can then change the password, at which point you can now safely change the users password.
  3. Flag the password request as having been completed, or delete it. You could also track information like IP address or something similar if you are really concerned about who changed the password if you are really concerned about it.
  4. Send the user an email confirming that they have changed their password.

Also, just in case this isn't happening already, make sure you are hashing and salting the users password. It doesn't sound like you were doing that when you were just replacing the password with a GUID, so just double checking.

1
votes

Users also forget to reset passwords (things happen). Being paranoid about passwords, I'd suggest limiting the link lifetime to 24 hours. This should be more than enough. It doesn't solve the problem of malicious intercept but it is better than nothing.

1
votes

I would make the following suggestions:

  1. Require some piece of information before the user is allowed to click the forgot password button. For example, require an email address and date of birth.

    Ideally your user interface should not provide any feedback that allows a hacker to determine if his reset request succeeded. You don't want them farming your page for email addresses or DOBs. However this is a usability tradeoff, so whether you do this depends on how much security you really need.

    You might also considering requiring a captcha which makes brute force and application DoS attacks much more difficult.

  2. Expire the one-time token as quickly as possible. A couple hours is enough in my opinion. You should never consider email to be private-- it isn't, unless you are using a secure email technology (e.g. PGP) on top of the base protocol (most people do not). The last thing you want is for a black market to open up where your GUIDs are bought and sold, which is exactly what could happen if they have infinite lifespan.

  3. Do not use GUIDs. They are not cryptographically random and are guessable. I suggest you use a cryptographic random number generator and translate it into base64.