The always brilliant Ryan Bates from Railscasts had a recent episode: #274 Remember Me & Reset Password
I have a slightly different way of handling password resets and thought I’d share.
Ryan walks you through adding two attributes to a User model: password_reset_token and password_reset_sent_at
I am not a fan of adding extra columns to my database tables, especially if only a few records will use it temporarily.
If you follow along with episode #250 Authentication From Scratch , you should have two columns: an encrypted password column password_hash, and the salt password_salt.
What I do differently is use ActiveSupport::MessageVerifier to create self-expiring, encrypted tokens for password resets.
I encrypt a date in the future using the user’s current hashed password as the salt.
I then append the encrypted date string to the end of the user’s id and encrypt that using the secret_token that Rails generates on app creation (in config/initializers/secret_token.rb).
In the User model, I add two methods to do this:
def generate_token ActiveSupport::MessageVerifier.new(Rails.configuration.secret_token).generate([id, 1.day.from_now, password_digest]) end def self.find_by_token(token) begin user_id, expiration = ActiveSupport::MessageVerifier.new(Rails.configuration.secret_token).verify(token) if expiration.future? Member.find(user_id) end rescue nil end end
Example:
the_user = User.first reset_token = the_user.generate_token # Mail reset_token to the_user # … verify token and reset password: if the_user = User.find_by_token(reset_token) # reset_token is valid, OK to change password the_user.password = new_password end
Pros:
- No extra, unused columns in your database tables.
- An attacker needs two keys ( user’s password digest and your apps secret_token) to compromise a user account.
- Once a password is changed on a user account, all tokens for that user are immediately invalid
Con:
- If an attacker gains access to a copy of your users table and codebase you’ll have to regenerate your secret_token to prevent them from being able to reset *all* the user passwords