Forty minutes ago I received a message from Facebook where my good friend Ivan asked me his Facebook friendship again. I asked him what was going on and he simply said that he did try to reset his password but he failed because he couldn't remember his very first password. In short, the system simply blocked his actions because he exceeded the limit of attempts. This is a normal procedure due to the prevention of malicious attacks against one user's credentials. However, this also happened because the application's password recovery works only on a single level, not on multiple levels. In this post I'm going to explain you why a multilevel approach is a better way to handle password recovery.
Private and public levels
A web application should be able to distinguish between a private and a public level when it comes to user credentials. If you work on a social network like Facebook or Twitter, some login credentials can actually be public or private, depending on user's choice. On Facebook, you can login either using your email or your username. The email address can be either be private or public, depending on the user's settings of his/her profile. Further, the username can be actually obtained from the last part of the public profile page URL, and this is something that a user cannot change because Facebook doesn't allow you to explicitly prevent search engines from indexing your public profile page.
A better solution would be to provide a user with a secret random token to be used only for password recovery. This token should work only on a private level, exactly how Gmail does for its password recovery or, more generally, how Twitter handles its APIs. A secret token is surely more robust and consistent than running a comparison between the old and new password, because a user can actually forget his/her new or old password (or even both). Ivan wasn't able to log in again because he forgot his new password and the system (correctly) interpreted his later attempts as a malicious action.
Further, a good system should also provide a way to hint the old password through the use of some human words or phrases known only to the user. This also works on a private level. Asking a user what's his/her mother's first name is a good example of this, but this technique could be improved a lot if we let the user free to choose the question and the answer because only in this case we'll be sure that this will be something completely random and unpredictable.
Finally, we need to provide a fallback level when all the aforementioned techniques don't work and a user can't still reset his/her password. This can be actually done through a direct human action from the application development team. It's still a recommended practice to set up a support section on our application where a user can get all the information he/she needs to fix a problem or simply chat with a member of the support team to get his/her problem solved.