< Back
Jun 07, 2017

On password resets

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.

  1. Render a 'forgot password' page. This is basically the new REST action for your password_resets controller. Find the user by the email address the submit through the form.

  2. In the create action, create a reset_token (string) and reset_expiry (date, set to an hour or two in the future) and save them into the appropriate columns in your user database. Send the user a URL with the reset_token as a param.

  3. Create a route to handle the URL you sent the user. This will be your REST edit action. In your controller, find the user by searching for both the reset_token in the URL's params and for the reset_expiry still 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.

  4. In your update action, 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_token and reset_expiry to something falsey (e.g. nil or 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_at instead. Then, in your edit and 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.