How to Setup AuthLogic In Rails 3 Posted on October 6th, 2010
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