8

This is yet another "How is a forgot my password page done right" question. However, it explicitly addresses two implementation options that I have come across recently.

The common approaches [1,2] that I know are based on a random token that can only be used once and that has a limited validity:

  • User visits forgot password page
  • User provides email address (or similar)
  • If the email is associated with a valid user account
    • The app generates a random, unique token t. t is of sufficient entropy (say 128 Bit).
    • The app stores the random token t and the timestamp it was created on alongside the user in the database
    • The app sends an email containing a password reset link to the users' email address. The link looks something like this: https://foo.bar/resetPassword?token=t
  • User clicks on the link in the email
  • The app checks
    • if there is a token t inside the database for a given user
    • if yes, checks that it is still valid based on the stored timestamp
    • if yes, allows the user to access the resetPassword page
  • The user sets a new password
  • After that, the app invalidates the token t

You can add a little more security stuff like out of band channels or security questions. But for me, that is the common best practise that I am aware of.

Now consider a different approach. We will loose the single use property. However, the benefit is that the app does not have to keep track of the token states at all.

Instead of generating a random token, we will use an HMAC as follows:

  • token = HMAC_k(email, timestamp)

k is a secret that the app knows of sufficient entropy. The reset link which is emailed to the user then looks as follows:

When the user accesses the link, the app verifies it as follows:

  • calculate HMAC based on URL parameters: token_calculated = HMAC_k(email, timestamp)
  • check if the token provided as URL parameter and the calculated HMAC match
  • if yes, check if token is still valid based on the timestamp
  • if yes, allow the user to access the resetPassword page

In my opinion, the property of not having to maintain a server-side state is more valueable than the single use property. This assumes that a reasonable, short validity of tokens is configured at the server-side - say 20 to 30 minutes.

I would really like to know what other people think of the second approach. Especially, if there are any drawbacks that I might have not noticed yet.

[1] https://www.owasp.org/index.php/Forgot_Password_Cheat_Sheet

[2] http://www.troyhunt.com/2012/05/everything-you-ever-wanted-to-know.html

7

Your question is well written, and you clearly understand the issues well. There is nothing fundamentally wrong with the crypto token approach you describe. Some things to be aware of:

  • Most sites these days are database intensive, so storing a password reset token is little additional overhead.
  • You should throttle the number of password reset emails that can be generated (e.g. max of 2 per 30 mins, for each email address) - which requires writing to the database.
  • You are reliant on k remaining secret; this could be copied by a malicious administrator (or brute forced, if weak) - while database tokens avoid this single point of failure.
  • A common (albeit minor) weakness with password reset schemes is the leakage of the token to external sites in the referer header. Having the token be strictly single use can help with this - although you can fix this weakness with crypto tokens as well.

There is a similar choice with generating session cookies - using crypto or the database. In fact, the performance considerations are much more significant for session cookies, as this work is done for every request. Despite the overhead, most sites use database tokens, for the minor security advantages.

1

It is not necessary to lose the one-time property. Imagine that the password reset token is (conceptually, add details): HMAC(email, timestamp, current_password_hash), where current password hash is the hash that you store in your database (hopefully based on some KDF) and this is the password that the user does not know.

When the user changes password, the value of this hash will be different, and thus the token will not be valid anymore. Even if user sets the same password (though unlikely, as they should have tried it) a proper implementation of password hashing should include a nonce, and the password hash will be different after the reset, even though the password is the same. This is basically exploiting the fact that you already store some state in the database (the password hash) and you may skip storing another one.

P.S. k really should remain secret - this is a major concern :)

  • On a side note, email is in general not secure transport, so if you have any form of second factor auth available, this is the place to use it (after link is verified, before password reset). Another, composable, option is to set a unique token in a httponly secure cookie when you offer the reset form and include it in the signature. In the mail do not send a link, but just a token and request the user to copy paste it in a new form in the same browser window - basically name sure this is the same session. – Petar Donchev May 4 '17 at 13:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.