Tutorial: Reset passwords with Authlogic the RESTful way

I’ve been getting emails asking me how to reset passwords with Authlogic, or how to confirm accounts. In this tutorial I’ll cover resetting passwords, since it is more complex, but after reading this tutorial there is no reason why you couldn’t set up account confirmation as well. In fact, my next tutorial will cover just that.

What am I about to read?

You are going to read a tutorial on how to reset passwords the RESTful way. I am going to pick up where I left off on the Authlogic basic setup tutorial, so if you have not read that I highly recommend doing so.

Want to see it in action before you start? Check it out for yourself:

A live example of this tutorial

Before we begin, let me walk you through the basic process of resetting a password as I see it:

  1. A user requests a password reset
  2. An email is sent to them with a link to reset their password
  3. The user verifies their identity be using the link we emailed them
  4. They are presented with a basic form to change their password
  5. After they change their password we log them in, redirect them to their account, and expire the URL we just sent them

That seems pretty simple, right? So let’s get started…

Helpful links

Before we start I want give you some helpful / related links:

1. Add in the reset_password_token and email fields

I am going to pick up from the Authlogic basic setup tutorial, so I am assuming you are familiar with Authlogic and the basic setup.

With this tutorial I am introducing a new field in your database called perishable_token. Authlogic will take care of maintaining this for you. Here is how:

  1. The token gets set to a unique value before validation, which constantly changes the token. The value is a “friendly” value, something like: 4LiXF7FiGUppIPubBPey, not as long or as hardcore as a Sha512 hash, so it doesn’t cause problems when emailed or copying and pasting into a browser.
  2. After a session is successfully saved (aka logged in) the the token will be reset. This makes the email we sent them before no longer usable.

Generate your migration:

$ script/generate migration add_users_password_reset_fields

Now update the migration:

class AddUsersPasswordResetFields < ActiveRecord::Migration
def self.up
add_column :users, :perishable_token, :string, :default => "", :null => false
add_column :users, :email, :string, :default => "", :null => false

add_index :users, :perishable_token
add_index :users, :email
end

def self.down
remove_column :users, :perishable_token
remove_column :users, :email
end
end

Migrate your database:

$ rake db:migrate

When adding the email field Authlogic will notice this and validate it for you, making sure the email is unique and a valid email address. If you wish to disable this just pass the :validate_email_field => false option to acts_as_authentic. You can also have it ignore the email field all together by passing the :email_field => nil option.

2. Let users request a password reset

We need to know if users lost their password, why not create another resource for this?

$ script/generate controller password_resets

Let’s fill out this controller with some actions (I will explain them below):

Now update your routes:

# app/controllers/password_resets_controller.rb
class PasswordResetsController < ApplicationController
def new
render
end

def create
@user = User.find_by_email(params[:email])
if @user
@user.deliver_password_reset_instructions!
flash[:notice] = "Instructions to reset your password have been emailed to you. " +
"Please check your email."
redirect_to root_url
else
flash[:notice] = "No user was found with that email address"
render :action => :new
end
end
end

What did I just do?

The deliver_password_reset_instructions! is a method I added, you can call it whatever you want. All that it does is reset the password reset token and send an email.

Here is what I did in my own application:

# app/models/user.rb
class User < ActiveRecord::Base
def deliver_password_reset_instructions!
reset_perishable_token!
Notifier.deliver_password_reset_instructions(self)
end
end

The reset_perishable_token! is a handy method Authlogic gives you. It basically resets the field to a unique “friendly” token and then saves the record.

My Notifier action looks like this:

# app/models/notifier.rb
class Notifier < ActionMailer::Base
default_url_options[:host] = "authlogic_example.binarylogic.com"

def password_reset_instructions(user)
subject       "Password Reset Instructions"
from          "Binary Logic Notifier "
recipients    user.email
sent_on       Time.now
body          :edit_password_reset_url => edit_password_reset_url(user.perishable_token)
end
end

Then the email looks like:

# app/views/notifier/password_reset_instructions.erb
A request to reset your password has been made.
If you did not make this request, simply ignore this email.
If you did make this request just click the link below:

If the above URL does not work try copying and pasting it into your browser.
If you continue to have problem please feel free to contact us.

You can do the above any way you want. I actually recommend creating a text and html version of this email, but for the sake of brevity I only included the text version.

3. Let users reset their password

Once they get that email and click the link in it, it needs to take them somewhere. So let’s add some methods to our PasswordResetsController:

# app/controllers/password_resets_controller.rb
before_filter :load_user_using_perishable_token, :only => [:edit, :update]

def edit
render
end

def update
@user.password = params[:user][:password]
@user.password_confirmation = params[:user][: password_confirmation]
if @user.save
flash[:notice] = "Password successfully updated"
redirect_to account_url
else
render :action => :edit
end
end

private
def load_user_using_perishable_token
@user = User.find_using_perishable_token(params[:id])
unless @user
flash[:notice] = "We're sorry, but we could not locate your account. " +
"If you are having issues try copying and pasting the URL " +
"from your email into your browser or restarting the " +
"reset password process."
redirect_to root_url
end
end

What did I just do?

  1. You really don’t need to add the edit method because its a blank method, the before_filter takes care of everything we need to do there. There is only a view for this method.
  2. The update method is nice, because if the user is successfully saved, Authlogic will automatically log them in, keeping your PasswordResetsController focused on resetting passwords, not sessions.
  3. The load_user_using_perishable_token has a unique method: find_using_perishable_token. This is a special method that Authlogic gives you. Here is what it does for extra security:
    1. Ignores blank tokens
    2. Only finds records that match the token and have an updated_at (if present) value that is not older than 10 minutes. This way, if someone gets the data in your database any valid perishable tokens will expire in 10 minutes. Chances are they will expire quicker because the token is changing during user activity as well.

4. Restrict access

Now we just need to make sure the user is logged out when accessing these methods. Modify your before filters in your PasswordResetsController to look like:

before_filter :require_no_user

Where are the views?

Instead of cluttering up this tutorial with a bunch of basic views, I left them out. There is nothing crazy going on in the views, nor do I really need to explain anything. If you noticed in the helpful links section I have a live example of this tutorial.

Here is a direct link to the users views source code, you can copy the views over from here

Why not reset passwords this way…?

If you care, here are my thoughts behind this tutorial and ultimately why I recommend resetting passwords this way…

My goal with Authlogic was to stay away from generating code and give you the proper tools so that you could easily and effectively write the code yourself. If you noticed, in this tutorial, all that we really did was add a simple PasswordResetsController. Most of this tutorial was explaining what was going on, so you could understand what you were doing. I can’t write that controller for you, because it is application specific. I want to keep the authentication logic in Authlogic and your application logic in your application. The tutorial above produces simpler and cleaner code than a generator. The most important part is that you understand what is going on and it works the way you want. I am basically laying out tools for you to use and explaining them. You get to use them how you wish. There is no “Surprise! Here are hundreds of lines of code, hope you like it”. If you want to change passwords a different way, go for it, you now know how Authlogic can assist you in your journey to resetting passwords.

Lastly, I know there are a few other common ways to reset passwords, so I will address those and why I decided against them:

  1. Changing / randomizing the password during the first step and then emailing them their new password
    You should NEVER change anyone’s password until they have proven they are that user. What if Billy 13 year old starts typing in random logins and resetting peoples passwords? Those people aren’t going to be happy.
  2. Using an already existing token that is not unique or perishable
    A lot of people are tempted to use the remember_token, single_access_token, or even the crypted_password for authenticating the user for a password change. There is a reason we have all of these and none of these should be used for resetting password. What if Billy 13 year old gets ahold of any of these, he will have full access to resetting that user’s password, and then have full access to their account. If for some reason he gets any of these things, your users should still be protected, which is why we need a unique and perishable token.
  • Share/Save/Bookmark


42 Responses to “Tutorial: Reset passwords with Authlogic the RESTful way”

  1. nice information.. good to read..
    Good Luck.

  2. Lakshan says:

    Nice tutorial Ben!

    Currently I’m converting my existing restful-authentication based apps to Authlogic. In this process, I was able to use forgot_password (git://github.com/greenisus/forgot_password) plugin with Authlogic without any issues. This plugin also approaches the problem in a similar fashion you have done and it’s too flexible enough to customize.

  3. Karl says:

    Ben,
    Authlogic is improving so nicely. Just let me iterate my appreciation for your work.

    Feature Request: now that you have added the password_reset_token, I was thinking how the same facility would work well for verification of new accounts. Of course, Authlogic doesn’t need to facilitate all the account status changing, but the same password_reset_token could easily be used for validation as well. The only thing I see that would be required is to change the default expiry time from 10.minutes to something optionable. Just add the option of specifying the expiry time to find_using_password_reset_token(:expires_in => 20.minutes). 10 minutes is probably OK, but I like to leave users just a little more time.

    Thoughts?

  4. Ben Johnson says:

    Hey Karl, I think thats a good idea. To be honest, you could use the password_reset_token for activation as well. Both tokens really are the same thing, so maybe they should share the same field? I’m thinking about renaming this to something a little more generic like "perishable_table"? I’m not sure yet, but I’ll give it some more thought.

    Also, the expiration time is configurable. I provided this option for acts_as_authentic:

    * <tt>password_reset_token_valid_for</tt> – default: 10.minutes,
    Authlogic gives you a sepcial method for finding records by the password reset token (see Authlogic::ORMAdapters::ActiveRecordAdapter::ActcsAsAuthentic::PasswordReset). In this method
    it checks for the age of the token. If the token is old than whatever you specify here, a user will NOT be returned. This way the tokens are perishable, thus making this system much
    more secure.

  5. Henrik N says:

    Instead of "# You do not need to add this method, I put it here so you know there is a view for this." I would recommend "render". Short, sweet and explicit.

    Also, it strikes me as a little weird that the Authlogic login is nice and RESTful, where this flow is not. How about a PasswordResetsController where new/create is for requesting the reset and edit/update is for actually resetting? Just how well this maps to REST can be discussed, but either way I think keeping it in its own controller makes things easier.

  6. Ben Johnson says:

    Hi Henrik, you make some good points. I actually started off the tutorial with a separate passwords controller. I’m gonna play around with that again and see if it makes the most sense. I just released a new version of Authlogic, so I was going to update this tutorial anyways. Maybe I’ll modify it to do that.

  7. Karl says:

    @Henrik:
    re:"How about a PasswordResetsController"

    I was thinking the exact same thing. Very RESTful, sir. Maybe it should be a nested resource under Account/User?

    But this really has nothing to do with Authlogic itself.

  8. Ben Johnson says:

    Alright, I updated the tutorial, both of you are right, this is a much cleaner approach.

  9. Charlie says:

    Karl, that’s exactly what I’m thinking. Probably a singular (rather than plural) resource directly below each user. Then requesting a password reset is basically sending an empty POST request to /users/karl/password_reset – as you said, it’s independent of Authlogic (one reason why I’m loving Authlogic already!) but I think that’s how I’ll want to implement it.

  10. Ben Johnson says:

    Hey Charlie,

    That should be a very easy change. It’s really up to you, go for it if you like that method better. Either way is fine.

  11. Tom K says:

    First of all, thanks for making Authlogic and for these tutorials… not only is the plugin an immensely better approach than the alternatives I looked at, but the RESTful nature of the tutorial means that folks new to Rails can integrate the functionality in a less fragile manner into their apps.

    That said, I’ve been banging my head trying to get this particular password reset to work, and it’s because I can’t seem to get the reset_perishable_token! method to show up for my Users class. I believe I’m using the latest version of Authlogic (I only installed it a few days ago) and I have a relatively simple setup.

    Registration, login, etc all work fine. And I know my email sending works fine… I just can’t get the perishable token to reset.

    My user class file has "acts_as_authentic :login_field => "name";" as the call to authlogic.

    As soon as I uncomment out the line in user.rb with "reset_perishable_token!", the page fails with error:

    <pre>undefined method `reset_perishable_token!’ for #<User:0×2503dfc></pre>

    Nor can I call the function from the console.

    I definitely have a perishable_token column in my db (mysql), and it’s indexed. I can’t figure out why I don’t have the "reset_perishable_token" or "reset_perishable_tokens" available in my user class… any ideas would be very welcome. Thanks.

  12. Ben Johnson says:

    The name of the method depends on the name of the field you are storing your perishable token. You might possibly be using remember_token. Try:

    reset_remember_token!

    Everything is based on the name of your fields so that it feels nice and clean.

  13. Tom K says:

    Thanks for responding…

    I have both remember_token and persistent_token fields in my user class, but there isn’t any method showing of the form "reset_FOO_token" at all. The user class definitely /seems/ like act_as_authentic is in effect – password and password confirm have to match to save, etc…

  14. Ben Johnson says:

    Hey Tom,

    Well you don’t need both, you need one or the other. That is strange and obviously something is off. Can you compare whats going on in your app to the authlogic_example on github? Since that is working.

  15. Michail says:

    Thanks for the great plugin!

    There is error in the code of section 3 (3. Let users reset their password).
    In the method update should be @user.password_confirmation = params[:user][:password_confirmation]
    But there is @user.confirm_password = params[:user][:confirm_password]

  16. Tim Johnson says:

    Ben, you have a couple default_url redirects there in the page, maybe better represented as "root_url" in keeping with the other tutorials on this site. I spent a couple hours reading through your stuff today, and I must say that the only thing more impressive than your code is your documentation ability. You have done a marvelous service to us all.

  17. Ben Johnson says:

    Tim, thanks for the kind remarks. I went ahead and changed default_url to root_url. Let me know if you have any other issues.

  18. stephen says:

    Hi,

    I’ve got the plugin working but I’m not receiving any email when I try to reset my password.

    I’ve done everything as you suggest.

    I’m running on my localost right now. I’ve changed the default url options in notifier.rb to this:

    default_url_options[:host] = ‘localhost’
    default_url_options[:port] = 3000
    default_url_options[:protocol] = ‘http’

    When I click on the forgot password button, I get redirected to the root_url and there is flash message telling me that the email has been sent and that I should check my emails.
    I’ve not had one yet, I’ve tried everything. Am I missing something? Does this work on localhost?

    Do I need to set up an action mailer? I don’t want to switc from restful_authentication if I can’t get this working.

    Thanks in advance, I do like the idea of usign this plugin, it seems a lot easier but restful_authentication does let me receive an email when I reset my password

    Steve

  19. Ben Johnson says:

    Hi Stephen,

    I can’t send email on my laptop without telling ActionMailer to use an smtp server, my ISP won’t let me send emails without routing it through an smtp. So I would try that. Lastly, Authlogic has nothing to do with how emails are sent, that is all rails. This is just a tutorial to show you the different tools Authlogic provides to you. Also, check your logs maybe there is something in there too.

  20. stephen says:

    Hey,

    thanks a lot for the reply, I feel so stupd. ha.
    i know how to fix the problem now. I will just reuse my smtp code and that shuold do the trick.

    thanks a lot,

    Steve

  21. Stephen says:

    Hey Ben!

    Great tool, documentation & tutorial. Much appreciated!

    Michail was right. Section 3 has a bug, and should read:

    def update

    @user.password_confirmation = params[:user][:password_confirmation]

    end

    Thanks!

  22. Stephen says:

    Sorry, the formatting hosed that line of code. Please feel free to edit this away.

    The line ought to read:

    <pre>@user.password_confirmation = params[:user][:password_confirmation]</pre>

    Thanks!
    -Steve-

  23. Charlie Park says:

    Ben -

    Thanks for these tutorials. If you’re able to make a note at the beginning of the tutorial regarding updates (like, the date of the latest update), it’ll help as new users (me) read through the comments and wonder "has he incorporated that change yet?"

    Thanks again.

  24. Mabed says:

    Impressive! Well done. I spent the weekend struggling with restful_authentication, what a waste of my time. But, in a couple of hours today, I got Authlogic integrated and working with my app.

    One Suggestion: Although the user is logged in immediately after sign up, which is really nice, still, send a conformation email which with a url/token which needs to be clicked prior to the next login.

  25. Ben Johnson says:

    Charlie, what update are you referring to? This tutorial should be up to date with the latest release.

    Mabed, that is not a problem. That feature should be optional since some want it and some done, and I accounted for that. Check out the magic methods section in the readme, there is a "confirmed?" method that authlogic automatically checks. I’m not sure if this is what you are talking about, but account confirmations should be just as easy, if not easier than resetting passwords. Again authlogic doesn’t force you to do anything you have the power to do whatever you want in your controllers, just like everything else in rails.

  26. Scott says:

    I added a simple password reset form with just old_password, password, and password_confirmation fields. Everything works fine until i try forcing the user to enter old_password. I think the problem is my valid_password line. I just can’t seem to get it to behave properly. I tried with and without the "self" on both "valid_password?" and "old_password".

    I guess the first question should be, am I on the right track, is there a cleaner way to set this up? If what I have is mostly correct, what am I doing wrong?

    attr_accessor :old_password

    validate :verify_old_password,
    :on => :update,
    :if => :password_required?

    def password_required?
    !self.password.blank?
    end

    def verify_old_password
    unless self.valid_password?(self.old_password)
    errors.add("old_password", "is not correct")
    end
    end

  27. Scott says:

    OK, I got my edit email form working better, but now have a different problem with the validations. My old_password_required method is conflicting with the perishable_token in this tutorial. Is there a way in a model to detect if a perishable_token was submitted? Is there a better way of doing this?

    Here is my model/controller/view so far. See lines 15-23.

    http://pastie.org/348073

  28. Mikhailov says:

    http://www.railsgeek.com/2009/1/6/generate-random-password-in-rails
    I am using Restful_authentication plugin for one of my projects.
    As part of my user creation workflow, system should to generate a random password for the new user.
    So, look at my password creation behaviour:
    * generate uncrypted password
    * send this with e-mail notification
    * crypt the password

  29. Abhishek says:

    Thanks Ben for this wonderful plug-in. I really appreciate your work.

  30. Maranatha says:

    Well Done!

    Everything works perfect, except for the validation when reseting the password, it allows to reset the password with empty values, the methods suggested in the documentation work when creating a new user but they do not when updating.

    Am I missing something or I should create my own validation in the User model

    Thanks

  31. Maranatha says:

    Well Done!

    Everything works perfect, except for the validation when reseting the password, it allows to reset the password with empty values, the methods suggested in the documentation work when creating a new user but they do not when updating.

    Am I missing something or I should create my own validation in the User model

    Thanks

  32. Marco says:

    Got to the same problem on the password reset. When the password fields are left empty, the password update is accepted. In the case of the user updating his/her profile, this is actually a feature when they don’t have to enter their password just to update their own details. But on password reset a password should be required.

  33. Hey, there’s a bug in the Step 3 code block.

    The user params has an extra space that needs to be removed.

    @user.password_confirmation = params[:user][: password_confirmation]

    … should be …

    @user.password_confirmation = params[:user][:password_confirmation]

  34. Hmm…. my comment got borked.

    Anyway, just make sure there are no spaces in the user params on Line 2 of the update method in password_resets_controller.rb.

  35. Ben says:

    I am having the worst time trying to test this. Specifically, I need to send an email when the account is created. My test looks like this:
    class NotifierTest < ActionMailer::TestCase
    test "new_user_created" do
    set_session_for(users(:one))
    @expected.subject = ‘New user account created’
    @expected.body = read_fixture(’new_user_created’)
    @expected.date = Time.now

    assert_equal @expected.encoded, Notifier.create_new_user_created(users(:one)).encoded
    end

    My error message looks like this:
    test_new_user_created(NotifierTest):
    NoMethodError: You have a nil object when you didn’t expect it!
    The error occurred while evaluating nil.session

    Any thoughts?

  36. Alexander says:

    It is written thath "The update method is nice, because if the user is successfully saved, Authlogic will automatically log them in, keeping your PasswordResetsController focused on resetting passwords, not sessions.", but it does not work. After saving user is still logged out.

  37. Ivan says:

    find_using_perishable_token should be updated to verify strict equality of token – or it could be in any case

  38. mironov says:

    I have the same problem as Alexander.

  39. Peter says:

    Hi,

    Is it necessary for the perishable_token to be changed even if a save was not successful?

    The problem I am having is when the user tries to set a new password (and confirmation), if that validation fails, the perishable_token is being changed and load_user_using_perishable_token no longer works when rendering the edit action.

    My hack is below, but wondering if it would be better to change the perishable_token after a successful save.

    <code>
    if @user.save
    flash[:notice] = "Password successfully updated"
    redirect_to account_url
    else
    # Get original token back
    @user.perishable_token = params[:token]
    render :action => :edit
    end
    </code>

  40. Peter says:

    Yikes, let’s try that code block again…

    <code>
    if @user.save<br />
    flash[:notice] = "Password successfully updated"<br />
    redirect_to account_url<br />
    else<br />
    # Get original token back<br />
    @user.perishable_token = params[:token]<br />
    render :action => :edit<br />
    end
    </code>

  41. GonZoo says:

    sorry for the bad wrote code, it’s possible to modify?

  42. Robby Colvin says:

    You mention at the top of the article that your next tutorial was going to be about account activation. I have searched and cannot find it. Is this written or should I just try and morph what you have here for that? Thanks!