views:

1876

answers:

7

I'm working on an application in ASP.NET, and was wondering specifically how I could implement a Password Reset function if I wanted to roll my own.

Specifically, I have the following questions:

  • What is a good way of generating a Unique ID that is hard to crack?
  • Should there be a timer attached to it? If so, how long should it be?
  • Should I record the IP address? Does it even matter?
  • What information should I ask for under the "Password Reset" screen ? Just Email address? Or maybe email address plus some piece of information that they 'know'? (Favorite team, puppy's name, etc)

Are there any other considerations I need to be aware of?

NB: Other questions have glossed over technical implementation entirely. Indeed the accepted answer glosses over the gory details. I hope that this question and subsequent answers will go into the gory details, and I hope by phrasing this question much more narrowly that the answers are less 'fluff' and more 'gore'.

Edit: Answers that also go into how such a table would be modeled and handled in SQL Server or any ASP.NET MVC links to an answer would be appreciated.

+2  A: 

You could send an email to user with a link. This link would contain some hard to guess string (like GUID). On server side you would also store the same string as you sent to user. Now when user presses on link you can find in your db entry with a same secret string and reset its password.

Sergej Andrejev
More details would be helpful.
George Stocker
+9  A: 

Generate a guid:

System.Guid.NewGuid().ToString()

Guids are (realistically) unique and statistically impossible to guess.

E-mail the guid to the users known e-mail address as a param on a querystring. Then you make sure it matches when it comes back. You should attach a time limit, but the length is up to you. "Something reasonable" would be on the order of 7 to 30 days. Also if the user successfully logs back in with their old password, you want to clear this as well.

You could record the IP address that uses the id for future reference in case you think someone may take legal action at some point if it is a cracking attempt, but you already have that in your webserver logs, right? So I don't see any point to this.

How hard you make it should depend on the value of the secret the password guards. If your site is storing someones financial information and the ability to transfer money around the globe, then I would ask for a little more information. If you're just building a forum or social networking type thing, e-mail address is probably fine. The attack vector is someone evil goes to the page and asks to reset the password and then mounts a man in the middle attack to intercept the reset e-mail, or they have already cracked the persons e-mail account. You have to weigh usability with the risks.

jeffamaphone
this is a great strategy. guid + ip address + perhaps user agent string or ServerVariables("AUTH_USER"). Maybe a salting+hashing of one with the other in the cookie.
p.campbell
You can't really require ip and UA string, since I may request the reset at work, e-mail delays may cause it to not get delivered right away, and then I use a different browser / IP to actually complete the reset from home.
jeffamaphone
Why 7-30 days for validity of URL?
George Stocker
There will be people who ask to reset and never actually follow through. You want to keep your db fairly clean, and you want to reasonably guard against someone obtaining the e-mail who shouldn't have it. The user can always re-request a reset and get another code.
jeffamaphone
Generally, if we are talking about an application with financials, I wouldn't recommend such an approach. Remember, plain e-mail is like sending a postcard - everyone in between can read it.
wilth
Yes. You can require them to verify other personal details when they submit the GUID to do "extra authentication." However, things like SSN, mother's maiden name, etc, are also generally available / easy to guess. Which is why banks force you to answer 3 to 6 personal questions and then ask them.
jeffamaphone
GUIDs are the wrong choice here, see my answer.
AviD
+1  A: 

A GUID sent to the email address of record is likely enough for most run-of-the-mill applications - with timeout even better.

After all, if the users emailbox has been compromised(i.e. a hacker has the logon/password for the email address), there is not much you can do about that.

EJB
+2  A: 

First, we need to know what you already know about the user. Obviously, you have a username and an old password. What else do you know? Do you have an email address? Do you have data regarding the user's favorite flower?

Assuming you have a username, password and working email address, you need to add two fields to your user table (assuming it is a database table): a date called new_passwd_expire and a string new_passwd_id.

Assuming you have the user's email address, when someone requests a password reset, you update the user table as follows:

new_passwd_expire = now() + some number of days
new_passwd_id = some random string of characters (see below)

Next, you send an email to the user at that address:

Dear so-and-so

Someone has requested a new password for user account <username> at <your website name>. If you did request this password reset, follow this link:

http://example.com/yourscript.lang?update=&lt;new_password_id>

If that link does not work you can go to http://example.com/yourscript.lang and enter the following into the form: <new_password_id>

If you did not request a password reset, you may ignore this email.

Thanks, yada yada

Now, coding yourscript.lang: This script needs a form. If the var update passed on the URL, the form just asks for the user's username and email address. If update is not passed, it asks for username, email address, and the id code sent in the email. You also ask for a new password (twice of course).

To verify the user's new password, you verify the username, email address, and the id code all match, that the request has not expired, and that the two new passwords match. If successful, you change the user's password to the new password and clear the password reset fields from the user table. Also be sure to log the user out/clear any login related cookies and redirect the user to the login page.

Essentially, the new_passwd_id field is a password that only works on the password reset page.

One potential improvement: you could remove <username> from the email. "Someone has request a password reset for an account at this email address...." Thus making the username something only the user knows if the email is intercepted. I didn't start off that way because if someone is attacking the account, they already know the username. This added obscurity stops man-in-the-middle attacks of opportunity in case someone malicious happens to intercept the email.

As for your questions:

generating the random string: It doesn't need to be extremely random. Any GUID generator or even md5(concat(salt,current_timestamp())) is sufficient, where salt is something on the user record like timestamp account was created. It has to be something the user can't see.

timer: Yes, you need this just to keep your database sane. No more than a week is really necessary but at least 2 days since you never know how long an email delay might last.

IP Address: Since the email could be delayed by days, IP address is only useful for logging, not for validation. If you want to log it, do so, otherwise you don't need it.

Reset Screen: See above.

Hope that covers it. Good luck.

jmucchiello
Wouldn't a potential attacker be able to use the MD5 of the current datestamp to get in?
George Stocker
Salt can fix that. I'll update....
jmucchiello
+1  A: 

1) For generating the unique id you could use Secure Hash Algorithm. 2) timer attached? Did you mean an Expiry for the reset pwd link? Yes you can have an Expiry set 3) You can ask for some more information other than the emailId to validate.. Like date of birth or some security questions 4) You could also generate random characters and ask to enter that also along with the request.. to make sure the password request is not automated by some spyware or things like that..

Java Guy
+11  A: 

First, do not immediately reset the user's password.

First, do not immediately reset the user's password when they request it. This is a security breech as someone could guess email addresses (i.e. your email address at the company) and reset passwords as a whelm. Best practices these days usually include a "confirmation" link sent to the user's email address, confirming they want to reset it. This link is where you want to send the Guid.NewGuid() link. I send mine with a link like: domain.com/user/verifypasswordreset/c2702a26d5d6447e948621f862f94b50

Yes, set a timeout on the link and store the guid and timeout on your backend. Timeouts of 3 days is the norm, and make sure to notify the user of 3 days at the web level when they request to reset.

Use the Guid pattern.

It is unique enough. If you want something much shorter, you'll have to generate your own hashing algorythm. And, make sure to eleminate any "real words" from the hash. I recall a special 6am phone call of where a woman received a certain "c" word in her "suppose to be random" hashed key that a developer did. Doh!

Entire procedure

  • User clicks "reset" password.
  • User is asked for an email.
  • User enters email and clicks send. Do not confirm or deny the email as this is bad practice as well. Simply say, "We have sent a password reset request if the email is verified." or something cryptik alike.
  • You create a Guid.NewGuid(), store it as a seperate entity in an ut_UserPasswordRequests table and link back to the user. So this so you can track old requests and inform the user that older links has expired.
  • Send the link to the email.

User gets the link, like http://domain.com/user/verifypasswordreset/c2702a26d5d6447e948621f862f94b50, and clicks it.

If the link is verified, you ask for a new password. Simple, and the user gets to set their own password. Or, set your own cyptik password here and inform them of their new password here (and email it to them).

eduncan911
+10  A: 

Lots of good answers here, I wont bother repeating it all...

Except for one issue, which is repeated by almost every answer here, even though its wrong:

Guids are (realistically) unique and statistically impossible to guess

This is not true, GUIDs are very weak identifiers, and should NOT be used to allow access to a user's account.
If you examine the structure, you get a total of 128 bits at most... which is not considered a lot nowadays.
Out of which the first half is typical invariant (for the generating system), and half of whats left is time-dependant (or somethign else similar).
All in all, its a very weak and easily bruteforced mechanism.

So don't use that!

Instead, simply use a cryptographically strong random number generator (System.Security.Cryptography.RNGCryptoServiceProvider), and get at least 256 bits of raw entropy.

All the rest, as the numerous other answers provided.

AviD
Absolutely agree, as far as I know, GUIDs were never designed to be cryptographically strong and impossible to guess.
Jan Soltis
well said, AFAIK MSDN clearly states that GUID should not be used for security.
dr. evil
+1 mixing in some form of hashing/crypto is recommended.
Ryan Duffield