98

We are implementing self password reset on a web application, and I know how I want to do it (email time limited password reset URL to users pre-registered email address).

My problem is that I can't find any references to point the developers at around using that technique. Can anyone point me in the direction of some good references on this technique?

AviD
  • 72,138
  • 22
  • 136
  • 218
bdg
  • 1,162
  • 1
  • 8
  • 9

8 Answers8

93

Some suggestions:

Don't reset the user's password until confirmed. Don't immediately reset the user's password. Only reset it once the user clicks on a confirmation link sent to their pre-registered email address.

Require a CAPTCHA. When a user requests that their password be reset, force them to solve a CAPTCHA before proceeding any further. This is to prevent automated tools from trying to give many users grief, and force the user to prove they are a human (not a robot).

Randomness. The time-limited password reset URL should include a random, unguessable component. Make sure you use crypto-quality randomness. The output from /dev/urandom or System.Security.Cryptography.RNGCryptoServiceProvider would be a good choice. The output from rand() or random() or System.Random are not random enough and would be a bad choice. A GUID or timestamp is not random enough and would not be a good choice.

Include a time limit. The reset confirmation link should expire after some reasonable time: say, 24 hours. The link should be usable only once, and should immediately expire as soon as it is used.

Include explanatory text in the email. You may want to add some explanatory text to the email, to explain why the email was sent, in case someone requests a reset for an account that is not your own. You could include some text like "Someone has requested that the password be reset for your account username on site. If you made this request, click here to change your password. If you did not make this request, click here to cancel the request."

Send email after the password is reset. Once the password is successfully reset, send email to the user to let them know that the password has been changed. Don't include the new password in that email.

Monitor cancellations. You might consider including some logic to monitor the frequency with which users click the cancellation link indicating that they didn't request a reset. If this goes above a certain threshold, it might be useful to send an alert to the system operators. Also, if a cancellation link for some request is visited after the confirmation link is visited, that's a potential indication of an attack against that user -- you may want to take action at that point, e.g., invalidate the user's password and require them to reset their password again. (This is a defense against the following attack: the attacker gains access to the user's mailbox, then requests that their password on your site be reset, then visits the confirmation link. If the attacker doesn't delete these emails from the user's inbox, then when the real user reads their email, they may click the cancellation link, giving you an indication of possible trouble.)

Use HTTPS. The link should use https (not http:), to protect against various attacks (e.g., Firesheep attacks on users surfing the web from an Internet cafe).

Log these operations. I suggest logging all such requests. In addition to logging the user's username, you may want to log the IP address of the client that requested a reset link be emailed to the user, as well as the IP address of the client that visited the reset link.

Additional reading. You may also want to read Troy Hunt's excellent blog post, Everything you ever wanted to know about building a secure password reset feature. Thanks to @coryT for a link to this resource.

Lastly, consider non-password authentication. Passwords have many problems as an authentication mechanism, and you might consider other methods of authenticating users, such as storing a secure persistent cookie on their machine with an unguessable secret to authenticate them. This way, there is no password to forget and no way for the user to be phished, though you do need to provide a way for a user to authorize access from a new machine or a new browser (possibly via email to the user's pre-registered email address). This survey paper has an excellent survey of many fallback authentication methods and their strengths and weaknesses.

D.W.
  • 98,420
  • 30
  • 267
  • 572
  • 3
    +1, also note that though it is popular (as evidenced by most of the wrong answers [on this SO question](http://stackoverflow.com/q/664673/10080) ), a GUID is *not* random enough. Also, I would just add a throttling mechanism, i.e. a user cannot request to reset his password e.g. 3000 times in one hour. – AviD Jan 30 '11 at 08:19
  • To summarize my conversation with @avid at his answer to that question, I'd say a version 4 GUID is indeed mostly random, and [I'd guess it is long enough](http://security.stackexchange.com/questions/1952/how-long-should-a-random-nonce-be). But it is probably silly and a bit dangerous to use since it was not originally designed for the task. And who knows where your code will be ported next? – nealmcb Jan 30 '11 at 21:31
  • It doesn't seem like `If you did not make this request, click here to cancel the request.` would be necessary for the email. – LarsTech Jul 19 '12 at 21:14
  • @nealmcb Here's a link from a guy on the C# compiler team that talks about GUIDs and that very strongly mentions that GUIDs are not by any means cryptographically random: http://blogs.msdn.com/b/ericlippert/archive/2012/05/07/guid-guide-part-three.aspx - there was another study that shows that Windows GUIDs are not cryptographically random: http://www.gotdotnet.ru/blogs/denish/1965/ - Bottom Line: Don't use GUIDs for Cryptographic purposes :) (I don't know if any non-Windows OS make guarantees in that aspect, but Unices usually have both /dev/random and /dev/urandom) – Michael Stum Jul 19 '12 at 21:16
  • Just how many characters should the random part be? – David Murdoch Jul 19 '12 at 21:28
  • 3
    "Don't include the new password in that email." If you have the user's password in clear text, you're doing it wrong. – Adriano Varoli Piazza Jul 19 '12 at 21:38
  • 1
    @DavidMurdoch, 64-80 bits should be plenty for the random part. (40 bits would actually probably be enough for this purpose, but why take any chances?) – D.W. Jul 21 '12 at 00:49
  • 1
    @DavidMurdoch See the link I posted above for more on D.W.'s answer (i.e. that a nonce of 64 bits is probably fine for this purpose): [How long should a random nonce be? - IT Security](http://security.stackexchange.com/questions/1952/how-long-should-a-random-nonce-be) – nealmcb Jul 26 '12 at 16:13
  • @MichaelStum Thanks for the links, and as I said it is a bad choice. But in terms of assessing the actual risk, Google translate didn't help much with that russian post by Nikolay Denishchenko on something like "Device and cryptanalysis UUID-Generator for Windows". Can you summarize the results in terms of how many unpredictable bits of entropy he thinks Windows version 4 GUIDs have, and how hard it would be to guess them remotely? – nealmcb Jul 26 '12 at 16:36
15

Troy Hunt has a very nice write-up on this exact question. Everything you ever wanted to know about building a secure password reset feature

coryT
  • 251
  • 2
  • 2
9

Any place that can send you a password means that they don't hash the password, but store it where it's somehow decryptable to 'plaintext'. This by itself is bad.

Probably not "most" secure, but more secure would be to:

Upon password request, send a password reset link to user with embedded GUID. The session in the GUID expires in, hmm, hour or so.

Rich Homolka
  • 199
  • 2
  • 3
  • Yeah no issues with it as I have session protected from SQL injection, so I send mail with well randomised password reset session code and allow hour to change it, that's OK. – Andrew Smith Jul 23 '12 at 17:40
  • 1
    @AndrewSmith - You should allow the user to select the new password not send it to them, as already explained, if your able to send the password to the user there is something broken about your security model. – Ramhound Jul 23 '12 at 18:47
  • This website currently generates a NEW password, sends by email, hash it and store in db, which is not good I believe. – Andrew Smith Jul 23 '12 at 18:52
  • generating a password and not expiring it on next connection is indeed a bad idea. – Lucas Kauffman Jul 23 '12 at 19:01
  • 1
    If this GUID is in plain text, then it can be obtained with sql injection and then you are defeating the whole purpose of password hashing. This post is a dangerous suggestion by means of ambiguity. – rook Jul 23 '12 at 19:38
  • So I should encrypt well randomized password reset session code and in window of 1h afterwards allow for change - no problem. – Andrew Smith Jul 23 '12 at 20:09
9

OK, so your question is, how should you structure the password/account recovery process? That will depend on what you want to optimize for: hassle-free user experience or good security.

If you want good security:

  • During signup, the user must enter his email address.
  • During signup, the user must enter a secondary channel for authentication -- mobile phone number or challenge question & answer (i.e. "what's your mothers maiden name" or better).

  • During recovery, your system first gets a rough verification of identity by means of the above secondary channel -- the challenge question, or by SMS'ing a code to the mobile phone, or similar.

  • When the first identity check above is cleared, the system sends a password reset email to the previously entered email address only. This is an additional, important measure to prevent f.x. Sarah Palin type exploits.

  • The email must not contain a new password or similar. It should have a click-able link to an HTTPS encrypted webpage on your site which a) is linked to the account via a non-guessable secret value provided in the URL, b) is time-limited, so the account recovery only works for x hours after it was requested, c) provides a way for the end user to create a new password.

  • Have good logging on all account reset transactions, and have a human being actually monitor these and act if they appear suspicious (look at the server logs for the IP addresses fx, do many requests come from the same IP address, are there instances were a user is trying to reset passwords from a country different from the registered account owner's etc).

You can also sidestep this complexity altogether: It's still early days, but OAuth / OpenID / sign in via Facebook, Google etc removes this complexity entirely from your systems, but with a perhaps less well established usability.

  • 2
    Challenge questions are the opposite of security. – David Murdoch Jul 19 '12 at 21:27
  • @DavidMurdoch: can you provide a reference that discusses this? I'd like to read more on it. – Marjan Venema Jul 20 '12 at 10:46
  • @David Murdoch : The challenge questions are password equivalent -- but the key question is to what. In my scheme they unlock a password reset mechanism which (presumably) *isn't under the attackers control*; the original email address. But true, password reset mechanisms generally trade reduced security for increased usability, perhaps I should have made that more clear. –  Jul 20 '12 at 10:58
  • Note that here in the dazzling future world of 2015, using a phone number as part of an MFA scheme has sort-of fallen apart, as the same small losable device serves as a gateway to both email and SMS. – Pointy Apr 03 '15 at 17:01
6

The most secure way to perform a password reset is to generate a large random unique one-time reset token, with an expiry date, tied to the user ID. The token should be hashed in the database, since an attacker with SQL access could use it to reset arbitrary user passwords.

A reset link should be sent to the email address, containing the reset token. When the user clicks the link, the token's hash should be found in the database, and the expiry date verified to be in the future. If these conditions are met, the user should be presented with a screen that allows them to type in a new password.

This entire process must be done over SSL, otherwise an attacker might sniff the URL and reset the password before the user does.

A few other tips:

  1. Secret questions a minor annoyance to attackers and a major annoyance to users. Avoid them completely.
  2. Present the user with a human verification challenge (e.g. a CAPTCHA) before sending the password reset. This prevents automated reset requests.
  3. Check whether the IP address performing the reset is one that has successfully logged into the account previously. If it's not, ask some further details about the account / user, e.g. account creation year, birth date, first line of address.
  4. Add an "I didn't ask for this email" link, so that users can report erroneous password reset requests.
Polynomial
  • 132,208
  • 43
  • 298
  • 379
  • Even if it is generated via SSL: you have no insurance that the e-mail itself is transferred over SSL and so this method has a huge flaw in transit -- the basic flaw of e-mail relay and retrieval. – Evan Carroll Jul 23 '12 at 22:19
  • 1
    @EvanCarroll "_the basic flaw of e-mail relay and **retrieval**._" regarding email **retrieval**: it is the responsibility of the user to use an email recovery address from an email provider with POPS, IMAPS, or HTTPS webmail support (and most email providers offer all three nowadays). – curiousguy Jul 24 '12 at 13:06
  • @EvanCarroll Whilst it's not an ideal situation, security is *always* secondary to usability. If you implement the world's most secure browser login, but it requires JavaScript, Flash, Java, ActiveX, Silverlight, and a degree in Computer Science to use, your users will switch off. Email is the de-facto standard for this kind of functionality, primarily because it's usable by everyone. The burden of security on a user's email system and personal network is *not* with me, nor should it be. – Polynomial Jul 24 '12 at 13:38
  • @curiousguy if plausible deniability is your goal, then yes. If security is your goal, then no. – Evan Carroll Jul 24 '12 at 18:55
  • @polynomial "de-facto" standards have nothing to do with security. That's just an argument to maintain the status quo oblivious to the security implications. – Evan Carroll Jul 24 '12 at 18:56
  • 3
    @EvanCarroll The argument for the status quo is that there is no acceptable alternative. – curiousguy Jul 24 '12 at 19:20
  • 1
    I agree with your answer, but why would you include the user-id into the link? If for example this user-id is an 8 character number, these 8 characters are equally easy to guess, as 8 additional random characters in your token. With the random characters you would not expose the user-id though. – martinstoeckli Jul 24 '12 at 20:25
  • @martinstoeckli True, but exposing the user ID isn't usually a big deal. You're likely to do it through the interface to your site anyway - look at StackExchange. Question 17542, answer 17547, your comment is 28508, you're user 8343, I'm user 5400, OP is user 10787. Trying to hide that info is difficult and would only introduce obscurity. In this case, though, the tokens should be unique, so the search should always return a single row regardless of whether the user ID is in the link. – Polynomial Jul 25 '12 at 05:54
6

Another one which may or may not be appropriate for your application, but is used in online banking applications and similar types of thing is to send half the reset code by SMS messaging to a registered mobile phone. From an attacker's perspective this is a complete PITA as it requires compromise of a couple of channels in order to break.

nealmcb
  • 20,544
  • 6
  • 69
  • 116
Rory Alsop
  • 61,367
  • 12
  • 115
  • 320
0

Make it a few steps procedure. For example something like this:

  1. User forgots his password. He clicks "I forgot my password send me an email what to do next" button (button label may differ ;))
  2. Your server sends him a link www.yourdomain.com/lostpassword?param_1=x;param_2=y
  3. He clicks the link and is able to create himself a new password
  4. He clicks submit. Everything done. Everybody happy

One and only catch is in point 3). X and Y values should be random, unpredictable and connected to this one particular account. They should be also one time use only and should become stale after a short period of time (24 hours?). Store both values in a dedicated table with the corresponding columns:

| some_id | user_id | X | Y | expire_time

If a user tries to use proper link check whether expire time is not met and if not, allow him to change the password.

ccellar
  • 103
  • 3
0

2 factor authentication via SMS ! 1 you know the customer and their mobie number, SMS them a unique code to add to the website to validate it is them :)