Logan Bailey

Adventures In Web Development

Blog, About, GitHub, and LinkedIn

This will go over, from start to finish, how to implement session handling using AuthLogic in Ruby On Rails 3.

Up And Running

To get up and running we'll need our new application, user model, user controller, and some basic styling so that nobody is too confused with what they're doing. Run the following in your terminal:

$ rails new login_app 
$ cd login_app 
$ vim Gemfile /* Add line "gem 'authlogic'" somewhere in the gem file */ 
$ bundle install

This will create an application called login_app, add the authlogic gem, and install the gem if it is not already installed.

Next we'll create our user model, controllers, and model classes.

$ rails generate scaffold user username:string email:string crypted_password:string password_salt:string persistence_token:string 
$ rake db:migrate

crypted_password and persistence_token are both expected attributes from authlogic. Now we'll need to setup our User Model to work as the authentication class.

class User < ActiveRecord::Base
	acts_as_authentic
end

Now that our Model is setup, lets work on the UI. Lets add a global register link so that anybody logged out can register easily. Open your /app/views/layouts/application.html.erb file to look like this:

<!DOCTYPE html>
<html>
<head>
	<title>LoginApp</title>
	<%= stylesheet_link_tag :all %>
	<%= javascript_include_tag :defaults %>
	<%= csrf_meta_tag %>
</head>
<body>

	<div id="nav">
		<%= link_to "Register", new_user_path%>
	</div>


	<%= yield %>

</body>
</html> 

If you haven't started your server yet feel free to start it now.

$ rails s

As you can see we're using the built in rails scaffolding. Since this is a basic application we'll have the user overview list act as our main page. Lets clean it up a bit, it currently shows encrypted passwords, salts, and tokens. Open your /app/views/users/index.html.erb and edit it to look like this:

<h1>Listing users</h1>
 
<table>
	<tr>
		<th>Username</th>
		<th>Email</th>
		<th></th>
		<th></th>
		<th></th>
	</tr>
 
	<% @users.each do |user| %>
		<tr>
			<td><%= user.username %></td>
			<td><%= user.email %></td>
			<td><%= link_to 'Show', user %></td>
			<td><%= link_to 'Edit', edit_user_path(user) %></td>
			<td><%= link_to 'Destroy', user, :confirm => 'Are you sure?', :method => :delete %></td>
		</tr>
	<% end %>
</table>
 
<br />
 
<%= link_to 'New User', new_user_path %> 

Now fire up your browser and check your nifty site. If you goto localhost:3000 you should see the default rails homepage, navigate to localhost:3000/users.

There are currently no users listed because nobody has registered. If you open up the registration page you'll see that it doesn't look much like anything, lets add some quick formatting there as well. Open up app/views/users/_form.html.erb and change it to look like this:

<%= form_for(@user) do |f| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% @user.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :username %><br />
    <%= f.text_field :username %>
  </div>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.text_field :email %>
  </div>
  <div class="field">
    <%= f.label :password %><br />
    <%= f.password_field :password %>
  </div>
  <div class="field">
      <%= f.label :password_confirmation %><br />
      <%= f.password_field :password_confirmation %>
    </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

The main changes here are rather simple. We changed the field name from :crypted_password to :password. Authlogic will map the :password field to :crypted_password after hashing it. We also changed the field type from f.text_field to f.password_field, this will create your standard password input field instead of a plain text input field. We have also added a :password_confirmation field. All of the logic to support these fields is built into authlogic. Try registering with an invalid e-mail address.

Authlogic adds in auto e-mail validation on email forms, as well as some other basic settings that you can change yourself.

Now lets create our first account. As you can see it has displayed our hashed passwords and some other data that no user really needs to be bothered with. Also the flash notice isn't exactly user friendly. To fix these open up app/controllers/user_controllers.rb and edit the create function to make it look like this.

def create
  @user = User.new(params[:user])

  respond_to do |format|
    if @user.save
      format.html { redirect_to(:users, :notice => 'Registration successfull.') }
      format.xml  { render :xml => @user, :status => :created, :location => @user }
    else
      format.html { render :action => "new" }
      format.xml  { render :xml => @user.errors, :status => :unprocessable_entity }
    end
  end
end

In rails 3 the :flash has been replaced with :notice. As you can see this syntax is a little clearer. We'll change this message to say “Registration Successful, a little more user friendly. Also we change the redirect to the users list page instead of the view user page. If you register another account you'll see a much cleaner UI interaction and interface.

Where's my session?

The Model

You may be saying, "That's neat where does the session handling come in". We're going to create a separate controller and model to handle our session handling logic. Since the user model contains all the information about our tracked object, we can just create an empty model called user_session, run the following command

$ rails g model user_session

You can delete the migration that was generated, it is not needed and only creates an empty table. Now lets setup our user_session model, edit your app/models/user_session.rb to look like this:

class UserSession < Authlogic::Session::Base
  def to_key
     new_record? ? nil : [ self.send(self.class.primary_key) ]
  end
  
  def persisted?
    false
  end
end

In rails 2 all that was required was changing the inherited class to AuthLogic::Session::Base, but with rails 3 there are some errors in the form handler. AuthLogic users a function called to_key which is clearly not defined. The function we have created does some ruby magic, if this is a new record it returns nil, as there is no key otherwise it calls send, basically saying self.primary_key. There is none on this object and we could most likely return nil all the time here, but this is nice to have anyways. Rails will also want to know if this object is persisted, it is not so we'll just return false.

The Controller

Well now that we have a way to track sessions, we do authlogic. Lets create a way to login and log out. We'll create a user_session controller to specifically handle this, once again we'll use ruby generate with some scaffolding.

$ rails g scaffold_controller user_session username:string password:string

This command creates a controller that expects its respective object to have two attributes: a password, and a username. As you can probably tell from now our UserSession class does not have either of these, but our User model has a username as well as a crypted_password that is mapped to password by authlogic. It also has already been setup to expect authlogic with the act_as_authentic function. If you open up your app/controllers/user_sessions_controller.rb you'll see it has tons of generated code in there, we're going to trim this down to a much nicer and more manageable file, that should look like this:

class UserSessionsController < ApplicationController

  # GET /user_sessions/new
  # GET /user_sessions/new.xml
  def new
    @user_session = UserSession.new

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @user_session }
    end
  end

  # POST /user_sessions
  # POST /user_sessions.xml
  def create
    @user_session = UserSession.new(params[:user_session])

    respond_to do |format|
      if @user_session.save
        format.html { redirect_to(:users, :notice => 'Login Successful') }
        format.xml  { render :xml => @user_session, :status => :created, :location => @user_session }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @user_session.errors, :status => :unprocessable_entity }
      end
    end
  end

  # DELETE /user_sessions/1
  # DELETE /user_sessions/1.xml
  def destroy
    @user_session = UserSession.find
    @user_session.destroy

    respond_to do |format|
      format.html { redirect_to(:users, :notice => 'Goodbye!') }
      format.xml  { head :ok }
    end
  end
end

The biggest changes here are in the destroy function. UserSession does not have an id, so one does not need to be passed in. Other than that, we just did some cosmetic changes, redirecting the create and destroy functions to to :users and changing their :flash text.

Logging In and Logging Out

Currently the login and logout functions are stored at user_session/create and user_session/destroy, not very pretty. Lets open up config/routes.rb and setup some cleaner urls. Your file should look like this:

LoginApp::Application.routes.draw do
	resources :users, :user_sessions
	match 'login' => 'user_sessions#new', :as => :login
	match 'logout' => 'user_sessions#destroy', :as => :logout
end 

We added :user_sessions to the resources, this is required for the login form submission. We then wrote to name match rules to change create routes for /login and /logout, this is a little more user friendly.

Now lets create the login link. We'll want this link available everywhere, right next to our Register link preferably. Open up app/views/layouts/application.html.erb, and change the body to look like this

<body>

    <div id="nav">
        <%= link_to "Register", new_user_path%> | 
        <%= link_to "Login", :login %>
    </div>


<%= yield %>

</body>

Since our route defines the login link as :login we can just refer to it as such, very easy and clean. There are couple more quick UI changes, in app/views/user_sessions/new.html.erb change the h1 tag to say "Login‚". In app/views/user_sessions/_form.html.erb change the password field to f.password_field just like we did in the register form.

Feel free to login now.

I Logged In But Nothing Happened?!?

It may seem that way, but we weren't displaying the notice. Change app/views/users/index.html.erb to look like this:

<h1>Listing users</h1>
<p id="notice"><%= notice %></p>
<table>
  <tr>
    <th>Username</th>
    <th>Email</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @users.each do |user| %>
  <tr>
    <td><%= user.username %></td>
    <td><%= user.email %></td>
    <td><%= link_to 'Show', user %></td>
    <td><%= link_to 'Edit', edit_user_path(user) %></td>
    <td><%= link_to 'Destroy', user, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>

<br />

<%= link_to 'New User', new_user_path %>

Now it will display what ever is in the notice variable. Lets also add a logout link, this should only be visible when the user is logged in. Change app/views/layouts/application.html.erb nav div to look like this:

<div id="nav">
    <% if current_user %>
    <%= link_to "Edit Profile", edit_user_path(current_user.id)%>
    <%= link_to "Logout", :logout%>
    <% else %>
    <%= link_to "Register", new_user_path%> | 
    <%= link_to "Login", :login %>
    <% end %>
</div>

current_user is a helper method that will return the current view's user model or nil. If there is a current user, we'll display an edit profile and a logout links, otherwise the reigster and login link. Now lets create that helper method, edit app/controllers/application_controller.rb so it looks like this:

class ApplicationController < ActionController::Base
  protect_from_forgery
  
  helper_method :current_user
  
  private
  
  def current_user_session
    return @current_user_session if defined?(@current_user_session)
    @current_user_session = UserSession.find
  end
  
  def current_user
    return @current_user if defined?(@current_user)
    @current_user = current_user_session && current_user_session.record
  end
end

current_user function is pretty much as we said earlier, it returns the current user object if viewer is logged in. It has global scope because of the helper_method :current_user function call. current_user_session returns the current_session if the user is logged in.

Now refresh your browser page, you should see the Edit Profile, and Logout links. Feel free to play around with this, you can see the complete working code for this sample at it's github repository: http://github.com/baileylo/login_app