Authlogic Account Activation Tutorial

I found a great tutorial explaining how to setup user activation with authlogic, but it was a little hard to read so I’ve dumped it all into one easier to read file.

Here’s the original: http://github.com/matthooks/authlogic-activation-tutorial/tree/master
Here’s my fork on github: http://github.com/clayton/authlogic-activation-tutorial/tree/master

Below is the formatted guide that is on github.

Remember, all credit goes to Matt Hooks and his original Authlogic Account Activation Tutorial

Introduction

This is an easier to read version of Matt Hooks’ Authlogic Activation Tutorial. The tutorial is divided into a number of steps and walks through the process of implementing user activation functionality into your pre-existing Rails app using Authlogic. If you are just starting out with Authlogic, be sure to checkout the Authlogic Example Tutorial.

Step 1

Let’s begin by adding an ‘active’ field with a default of false to the user model.

script/generate migration AddActiveToUsers active:boolean

1
2
3
4
5
6
7
8
9
10
  # new migration XXX_add_active_to_users.rb
  class AddActiveToUsers < ActiveRecord::Migration
    def self.up
      add_column :users, :active, :boolean, :default => false, :null => false
    end
 
    def self.down
      remove_column :users, :active
    end
  end

Step 2

Don’t forget to run the migraiton.

rake db:migrate

Authlogic automatically executes the following methods, if present, upon user action: active?, approved?, and confirmed?. Let’s create an “active?” method so we can hook into this magical goodness. And we should make sure that we protect the active attribute from mass-assignments by calling attr_accessible.

1
2
3
4
5
6
  # added to user.rb
  attr_accessible :login, :email, :password, :password_confirmation, :openid_identifier
 
  def active?
    active
  end

Step 3

Now try to log in. You should receive the error, “Your account is not active.” So far so good. Let’s make a controller to handle our activations:

script/generate controller activations new create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  # new file app/controllers/activations_controller.rb
  class ActivationsController < ApplicationController
    before_filter :require_no_user, :only => [:new, :create]
 
    def new
      @user = User.find_using_perishable_token(params[:activation_code], 1.week) || (raise Exception)
      raise Exception if @user.active?
    end
 
    def create
      @user = User.find(params[:id])
 
      raise Exception if @user.active?
 
      if @user.activate!
        @user.deliver_activation_confirmation!
        redirect_to account_url
      else
        render :action => :new
      end
    end
 
  end

Step 4

I raise exceptions in these actions to make sure that someone who is already active cannot re-activate their account and to deal with an invalid perishable token. I’ll leave it up to you how you want to handle these errors — you should probably provide some sort of “My Token is Expired!” action that will reset the token and resend the activation email if the user does not get around to activating right away.

Going down the list, let’s define the missing actions. First:

1
2
3
4
5
  # added to user.rb
  def activate!
    self.active = true
    save
  end

Step 5

Next, let’s make sure our user gets an e-mail with his activation code when he signs up. How are we getting our activation code? The same way we get our password reset code — through our perishable token:

Update: If you are experiencing the “Missing host to link to! Please provide :host parameter ” error see this post Missing host to link to! Please provide :host parameter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  # added to app/models/user.rb
  def deliver_activation_instructions!
    reset_perishable_token!
    Notifier.deliver_activation_instructions(self)
  end
 
  def deliver_activation_confirmation!
    reset_perishable_token!
    Notifier.deliver_activation_confirmation(self)
  end
 
  # added to app/models/notifier.rb
  def activation_instructions(user)
    subject       "Activation Instructions"
    from          "Binary Logic Notifier <noreply@binarylogic.com>"
    recipients    user.email
    sent_on       Time.now
    body          :account_activation_url => register_url(user.perishable_token)
  end
 
  def activation_confirmation(user)
    subject       "Activation Complete"
    from          "Binary Logic Notifier <noreply@binarylogic.com>"
    recipients    user.email
    sent_on       Time.now
    body          :root_url => root_url
  end
 
  # added to config/routes.rb
  map.register '/register/:activation_code', :controller => 'activations', :action => 'new'
  map.activate '/activate/:id', :controller => 'activations', :action => 'create'
 
  <!-- new file app/views/notifier/activation_instructions.erb --> 
  Thank you for creating an account! Click the url below to activate your account!
 
  <%= @account_activation_url %>
 
  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.
 
  <!-- new file app/views/notifier/activation_confirmation.erb -->
  Your account has been activated.
 
  <%= @root_url %>
 
  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.

Step 6

Now let’s modify the user create action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  # modified app/controllers/users_controller.rb
  def create
    @user = User.new(params[:user])
 
    # Saving without session maintenance to skip
    # auto-login which can't happen here because
    # the User has not yet been activated
    if @user.save_without_session_maintenance
      @user.deliver_activation_instructions!
      flash[:notice] = "Your account has been created. Please check your e-mail for your account activation instructions!"
      redirect_to root_url
    else
      render :action => :new
    end
  end

Step 7

As the comment says, we don’t need the Authlogic auto-login to take place so we save without maintaining the session. Now let’s define the ‘register’ view.

1
2
3
4
5
6
7
8
  <!-- new file app/views/activations/new.html.erb -->
 
  <h1>Activate your account</h1>
 
  <% form_for @user, :url => activate_path(@user.id), :html => { :method => :post } do |f| %>
  	<%= f.error_messages %>
  	<%= f.submit "Activate" %>
  <% end %>

Step 8

Let’s see if things are working…

… (processing) …

Looks like our user got activated!

But there’s a slight problem. Since we didn’t update the user’s password, we didn’t get a magical Authlogic auto-login! How rude.

At this point it’s perfectly fine to let the user log themselves in. And you can certainly simplify the activation down to one action so the user doesn’t have to click another button. But, I like Authlogic’s session maintenance. I also like short signup forms. So let’s kill two birds with one stone.

Let’s set up the user creation form to only ask for a user’s login/email. Then, let’s ask the user to set their password/openid upon activation, which will log them in automatically.

First, let’s change our acts_as_authentic call to only check for password length on update if the user has no credentials set.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
  # modified user.rb
  # For authlogic 2.0+
  acts_as_authentic do |c|
    c.validates_length_of_password_field_options = {:on => :update, :minimum => 4, :if => :has_no_credentials?}
    c.validates_length_of_password_confirmation_field_options = {:on => :update, :minimum => 4, :if => :has_no_credentials?}
  end
 
  # Pre-authlogic 2.0
  # acts_as_authentic :login_field_validation_options => { :if => :openid_identifier_blank? },
  #                   :password_field_validation_options => { :if => :openid_identifier_blank? },
  #                   :password_field_validates_length_of_options => { :on => :update, :if => :has_no_credentials? }
 
  # ...
  # we need to make sure that either a password or openid gets set
  # when the user activates his account
  def has_no_credentials?
    self.crypted_password.blank? && self.openid_identifier.blank?
  end
 
  # ...
  # now let's define a couple of methods in the user model. The first
  # will take care of setting any data that you want to happen at signup
  # (aka before activation)
  def signup!(params)
    self.login = params[:user][:login]
    self.email = params[:user][:email]
    save_without_session_maintenance
  end
 
  # the second will take care of setting any data that you want to happen
  # at activation. at the very least this will be setting active to true
  # and setting a pass, openid, or both.
  def activate!(params)
    self.active = true
    self.password = params[:user][:password]
    self.password_confirmation = params[:user][:password_confirmation]
    self.openid_identifier = params[:user][:openid_identifier]
    save
  end
 
  # modified activations_controller.rb
  def create
    @user = User.find(params[:id])
 
    raise Exception if @user.active?
 
    if @user.activate!(params)
      @user.deliver_activation_confirmation!
      flash[:notice] = "Your account has been activated."
      redirect_to account_url
    else
      render :action => :new
    end
  end
 
  # modified users_controller.rb
  def create
    @user = User.new
 
    if @user.signup!(params)
      @user.deliver_activation_instructions!
      flash[:notice] = "Your account has been created. Please check your e-mail for your account activation instructions!"
      redirect_to root_url
    else
      render :action => :new
    end
  end

Step 10

Now we need to update our views to reflect the new signup process.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  <!-- modified app/views/activations/new.html.erb -->
  <h1>Activate your account</h1>
 
  <% form_for @user, :url => activate_path(@user.id), :html => { :method => :post } do |form| %>
  	<%= form.error_messages %>
  	<%= render :partial => "form", :locals => { :form => form }%>
  	<%= form.submit "Activate" %>
  <% end %>
 
  <!-- new file app/views/activations/_form.html.erb -->
  <%= form.label :password, "Set your password" %><br />
  <%= form.password_field :password %><br />
  <br />
  <%= form.label :password_confirmation %><br />
  <%= form.password_field :password_confirmation %><br />
  <br />
  <%= form.label :openid_identifier, "Or use OpenID instead of your email / password" %><br />
  <%= form.text_field :openid_identifier %><br />

The End

And that’s it! Let me know if you have any suggestions for improvement.

Comments

  1. about2flip says:

    Need Help. Does this line go into a model called modifier.rb?

    1. added to app/models/notifier.rb
      def activation_instructions(user)
      subject “Activation Instructions”
      from “Binary Logic Notifier “
      recipients user.email
      sent_on Time.now
      body :account_activation_url => register_url(user.perishable_token)
      end

      def activation_confirmation(user)
      subject “Activation Complete”
      from “Binary Logic Notifier “
      recipients user.email
      sent_on Time.now
      body :root_url => root_ur

  2. about2flip says:

    I meant notifier.rb. Also Am I suppose to put pershibable token in somewhere? I am getting this error

    undefined method `reset_perishable_token!’

    Please help.

    Thanks

    • Clayton says:

      In this case notifier.rb is a mailer. So your notifier.rb file will look something like this

      class Notifier < ActionMailer::Base
      def activation_instructions(user)
        subject âActivation Instructionsâ
        from âBinary Logic Notifier â
        recipients user.email
        sent_on Time.now
        body :account_activation_url => register_url(user.perishable_token)
      end
      
      def activation_confirmation(user)
        subject âActivation Completeâ
        from âBinary Logic Notifier â
        recipients user.email
        sent_on Time.now
        body :root_url => root_url
      end
      
      end
      
  3. Ryan says:

    Getting this error:

    “Missing host to link to! Please provide :host parameter or set default_url_options[:host]“

    Seems to be something wrong with the register_url command in the activation_instructions method in the model. Have you seen this before or know what this is?

  4. Donovan Bray says:

    I had to add a simple migration to avoid ‘crypted_password’ and ‘password_salt’ cannot be null on saving the unactivated model.

    http://gist.github.com/229014

  5. Jonathan says:

    How would one go about auto logging the user after account activation (without setting the password on activation)?

    Authlogic’s code is a bit too complicated for me to understand just yet, and I can’t really figure it out.

  6. Rok says:

    Had the same issue as Jonathan, so I checked Authlogic docs, here’s my activate! and singup! methods

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    	def signup!(params)
    		self.login = params[:user][:login]
    		self.email = params[:user][:email]
    		self.password = params[:user][:password]
    		save_without_session_maintenance
    	end
     
    	def activate!
    		self.active = true
    		UserSession.create!(self)
    	end
  7. Ulf says:

    Thanks for your tutorial!

    I try to write a controller spec for create. I wonder how signup! can be mocked/stubbed? Something like
    User.stub!(:signup!).and_return(“true”)
    doesn’t work.

  8. Thanks – very helpful article. If following this precisely, I think there’s a step or two needed for app/views/users . The form partial requests the password, but that’s not what the description of the process says. So we probably need to copy and trim the form (because views/users/edit.html.erb probably still needs to allow password updates, unless we have an openid record, while the new.html.erb needs to request only the email address?)

  9. after using reset_perishable_token!
    i am getting different value of perishable token in db and in the e-mail
    and when user visit the link due to the unmatched values user get error user not find in my production log
    why is this?
    i am using exactly your code for activation_instructions

  10. Jonas says:

    Thanks for the tutorial, and thanks Rok for the tip on automatically creating user sessions. By calling activate! in the “new” controller method and using UserSession.create!(self) in the user model I bypass the confirm page entirely and just give the user a confirmation message in the flash….great! Eliminates a step for the user and the need for an extra view & method/route.

    Its not quite as secure as making the user sign in again, but equally as secure as having the user set the password after confirmation, I believe.

  11. Daz says:

    Yeah, thanks for the tutorial. I’m new to RoR and am trying to integrate this into Rails 3, and have a couple questions:

    - should the activation url that gets sent to the new user look something like this:
    http://127.0.0.1:3000/register.HAH88xS6gyT7SXsWKn4V
    I’m unable to pull the activation_code from the url –> params[:activation_code] isn’t doing the trick in the ‘new’ method found in activations_controller.

    -also, should I be expecting an email to be sent via Authlogic, or would there be additional work on my part?

    any help is greatly appreciated.

  12. Bogdan says:

    @Daz: try adding following to config/routes.rb
    match ‘register/:activation_code’ => “activations#new”, :as => :register

  13. joel Joseph says:

    Hi, i am new to ror . i followed this tutorial to for user activation . but i am getting this error.

    undefined method `deliver_activation_instructions’ for Notifier(id: integer, created_at: datetime, updated_at: datetime):Class

    can you please help me out?,,,,,

  14. Ae says:

    Great tutorial !
    Many thanks to Rok for a nice auto log in after activation solution.

    @joel Joseph
    Tutorial was meant to be used with Rails 2. The sending mail process has changed in Rails 3. RoR website have a great tutorial on Action Mailer http://guides.rubyonrails.org/action_mailer_basics.html
    hope that helps.

Trackbacks

  1. [...] doesn't come with anything such as this built in. It's simple enough to add and someone has written a tutorial already. The migration handles carrying the active status over from restful_authentication with a [...]

  2. [...] you’ve followed my version of the authlogic account activation tutorial or the original version by Matt Hooks you might have run into this error: Missing host to link to! [...]

Speak Your Mind

*