Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Time limited, or one time use, password reset tokens?

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.

like image 717
Chaddeus Avatar asked Oct 09 '13 02:10

Chaddeus


2 Answers

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.

like image 115
martinstoeckli Avatar answered Sep 17 '22 14:09

martinstoeckli


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.

like image 45
Darren Kopp Avatar answered Sep 20 '22 14:09

Darren Kopp