I've actually been working on some ideas around this area, so here's a dump of my thoughts so far. I apologise in advance for the ridiculous length of this answer.
Password reset mechanisms have to be designed with three major goals in mind:
For a good mechanism, we need to reach a balance between the three.
A few assertions that I'd like to make before starting:
Right, on to the actual answer!
1. Multi-factor authentication
Multi-factor authentication (MFA) is one of the most secure methods for account recovery, and human authentication in general. For those of you that are unfamiliar with MFA, it works on the principle that you must have more than one credential in order to authenticate:
- Something you know (e.g. password, secret answer)
- Something you have (e.g. key, hardware token, etc)
- Something you are (e.g. fingerprint)
Note that having multiple of one type, e.g. two passwords, or a password and a pin, does not count as MFA. I'm looking at you, online banking sites.
1.1. Something you know
When resetting a password, a common way to authenticate the user is to ask them some questions. Unfortunately, these questions are usually things like "What school did you go to?" and "What's your mother's maiden name?", which are incredibly easy for attackers to find out through social networking. Furthermore, many users dislike answering these questions when signing up, so they just hit a bunch of keys and invalidate the purpose of the question.
For e-commerce webapps, and other webapps that store private personal information (e.g. address / phone number), one might ask for that information. However, keep in mind that this information might be found online too.
An alternative would be to ask contextual questions, based on the user's experience on the site. For example, Hotmail asks for an email address that you have recently communicated with, as well as some other information about activity on your account. This is quite adaptable across most types of webapp, but isn't always applicable.
1.2. Something you have
Often sites will describe emailing a link or one-time password to your email address as a form of MFA. I assert that this is a false assumption. Users share passwords between accounts. This is an absolute fact, and you cannot prevent it. As such, you must assume that an attacker already has access to the user's email address. We'll cover the implications of this later.
The proliferation of cellphones has made them a great mechanism for two-factor authentication, as a form of out-of-band check. SMS reset tokens are easy for the user to understand and use, and an attacker having access to the phone is unlikely. However, this method is not without flaws. The biggest problem is when a user loses their phone, or changes provider. This completely invalidates the phone as a usable reset device. Another problem is that users don't always have their phone with them.
Using an app on smartphones partially fixes this problem. If a user changes their provider, the app remains on the phone. Allowing both SMS-based and app-based resets allows for a reasonable expectation of reliability.
Alternatively, allow the user to associate a login provider account (e.g. OpenID) and use a valid logon for that account as a "something you know" proof.
Other potential mechanisms:
- Physical token (e.g. RSA SecurID, USB token) - not exactly viable for most places, but useful for banks, internal corporate systems and other high-security situations.
- File - Randomly generated file given to the user on signup. Hash stored in database. Upon reset, user asked to provide file. Not the best usability or security, but potentially useful.
1.3. Something you are
This is where things get fun and sci-fi. Biometric authentication is something that has become more popular recently. Quite a few laptops include fingerprint scanners, and you can pick up cheap USB ones for a relatively small cost. Another popular biometric mechanism is facial recognition, via webcam. Both of these are excellent in terms of security when done right, but they both have a few problems:
- Most implementations are probabilistic, and therefore insecure. What stops an attacker from holding a photo of your face up to the camera?
- You can't rely on every user having a fingerprint scanner, and those that do aren't all compatible with each other. Furthermore, you're interfacing with hardware, so that requires a native app to talk to your webapp.
- The field of biometrics is relatively new, so there aren't any well-aged properly-studied implementations with fully understood security models.
All in all, the "something you are" model works for in-person identification, but isn't really feasible for webapps unless you're providing your customers with fingerprint scanners and appropriate software.
1.4. MFA best practices
Single-factor authentication is not enough. As we've seen from all the recent breaches it's difficult to keep passwords secure, and even when they're hashed they can still be broken. MFA is necessary for both login and password recovery. Mobile authentication for recovery purposes solves most problems, as long as the mobile device is still usable. In the case of mobile authentication not being possible, you must have a manual verification process in place, where they have to convince a human that they are the real deal.
2. Reset mechanisms
There are a multitude of different password reset methods out there, many of which fall way behind in terms of security. I'm going to discuss some that I've seen in the wild, explain their good and bad points, and propose some better ones.
2.1. Emailing the password back
The most basic mechanism is to just email the user's password to them. Please don't ever do this. It's horrible beyond measure. First, it requires that you're storing passwords in cleartext, or at least in a reversible format. You must hash passwords properly, and follow best practices for salting. Second, you're emailing the user's password in cleartext, over a cleartext protocol, over the internet. There's a special level of hell reserved for people who do this.
2.2. Generating a new password
Some sites generate a new random password, hash it, then email it off to the user. This isn't a good idea for quite a few reasons, though most of them can be mitigated in some way. The most fundamental issue is that you're emailing a usable password to a user in plaintext. Other issues with this method include:
- Many users are lazy, quite a few will reset and just tell the browser to remember the random password.
- A lot of sites don't force you to change the password upon first use of the password.
- An attacker having access to the email account has access to the password, regardless of what mechanisms you used to authenticate the user in the first place.
2.3. The reset link
This is one of the better (and, thankfully, more popular) mechanisms. It involves authenticating the user, then emailing them a one-time reset link. Once the reset link is used, the user is prompted to set a new password. Once that's been done, the link is invalid. Further security can be provided by including expiry times on the links, and enforcing that the link is invalid after the password is set or after the user's session times out.
The downfall of this method is when we remember our earlier assumption. The user's email account is not safe. It does not count as "something you know". Whilst an attacker's time window is short, they can still break in. Of course, all of this is further invalidated if (or, rather, when) the user forgets their email password.
2.4. Never leave the site
This is the holy grail, really, but can only be used alongside strong multi-factor authentication. Once the user authenticates themselves using a combination of security questions, out-of-band checks (e.g. cellphone), key files, biometrics, human verification, etc. they are immediately redirected to a password change form. This entirely bypasses the need to use email addresses at all.
If you truly believe that using an email address to send a link to is necessary, consider offering a no-email reset option which increases the number of checks that you require to be passed.
It's difficult to balance security, usability and reliability at the best of times, but this situation is directly dealing with a user that has already proven themselves to be unreliable. I'm not saying this in any kind of deprecating sense, but rather that we are all fallible.
It's important to keep a password reset system usable and simple, without skimping on security. Before implementing a massive 3-factor authentication process on every aspect of your webapp, think about what you're protecting. If you're just a small company selling things online, you can probably get away with basic 2-factor on recovery. If you're a major retailer, bank, or social networking site, you should be using strong 2-factor on recovery (multiple types of each), and 2-factor on logins from unrecognised sources.
In a sentence: keep it simple, think about your audience.