How password resets work
A short post explaining how password resets work, mostly just to help me remember.
It's not news that handling user services are a little complicated. I've always had a little trouble with all the moving parts. And if I'm honest, whenever hashes and digests come into play, those long garbled strings muddle up my brain so I can't follow what's going on.
So. Here are the basic steps to resetting a password.
- Render a 'forgot password' page. This is basically the
newREST action for your password_resets controller. Find the user by the email address the submit through the form.
- In the
createaction, create a
reset_expiry(date, set to an hour or two in the future) and save them into the appropriate columns in your
userdatabase. Send the user a URL with the
reset_tokenas a param.
- Create a route to handle the URL you sent the user. This will be your REST
editaction. In your controller, find the user by searching for both the
reset_tokenin the URL's params and for the
reset_expirystill being valid (e.g.
reset_expiry > Time.now()). If the user can't be found, flash an error and redirect the user somewhere else. If the database does return a user, render the password reset form.
- In your
updateaction, search for the user again, the same as in step 3. Change the user's password in the database (and make sure it gets saved!), and then set the
reset_expiryto something falsey (e.g.
undefined). Log the user in, then flash a success message and redirect the user to the homepage or account page or something.
The only real variation I've seen is in the
reset_expiry. You could, instead of saving a time in the future and searching for a users whose
reset_expiry hasn't expired, save the
reset_token_generated_atinstead. Then, in your
update actions, insert some middleware or a
before_action to check that the
reset_token_generated_at isn't expired.
This solution is arguably more modularized and easy to read. I don't know that modularization would be a huge problem in this case since not a lot of the password reset code can be used elsewhere, but readability is a big bonus. For this reason you'll see this solution employed in Michael Hartl's Rails Tutorial: Ruby is a language that emphasizes readability. Whereas the solution as I've written in in the steps above come from Wes Bos's Learn Node program--Node.js being a somewhat less human-friendly language from a reading perspective.