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.
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:
Before we begin, let me walk you through the basic process of resetting a password as I see it:
That seems pretty simple, right? So let’s get started…
Before we start I want give you some helpful / related links:
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:
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.
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.
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?
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
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
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:
nice information.. good to read..
Good Luck.
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.
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?
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.
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.
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.
@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.
Alright, I updated the tutorial, both of you are right, this is a much cleaner approach.
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.
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.
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.
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.
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…
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.
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]
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.
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.
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
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.
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
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!
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-
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.
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.
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.
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
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
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
Thanks Ben for this wonderful plug-in. I really appreciate your work.
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
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
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.
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]
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.
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?
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.
find_using_perishable_token should be updated to verify strict equality of token – or it could be in any case
I have the same problem as Alexander.
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>
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>
sorry for the bad wrote code, it’s possible to modify?
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!