Authentication

This document provides an overview for how to set up authentication and includes code for permitting login and logout functions. It is based on the method presented in the Rails 3.0 Agile Web Development book.

The document is written for Rails 3.0, but it should also work with Rails 3.1. Note, however, that Rails 3.1 provides a helper method (has_secure_password), which automatically gives the User model password attributes and an authenticate method.

User model

A user model is first created by generating a model or scaffold with the following physical attributes:

This can be created with the following command:

  rails g scaffold User name:string hashed_password:string salt:string

Additional attributes can be added for describing the user.

Don't forget the command rake db:migrate so that the table is actually created.

After the users table is created, code needs to be added to the user model (user.rb in the models folder) so that the model has virtual attributes (password and password_confirmation) and an authentication method. To add the needed model code, simply copy the model code to the user.rb file, replacing all of the previous contents. You may want to add additional validations and references depending on the needs of your application.

Using the User model

The rails console provides a good interface for learning how to use the user model and its methods. Here are examples that can be tried out on the console.

Creating a user

  u = User.new
  u.name = 'sam'
  u.password = 'secret'
  u.password_confirmation = 'secret'
  u.save

Note that the object won't save if the password doesn't match the password_confirmation. Try it!

In its current form, the scaffold views doesn't allow you to add new users. However, it can work after it is edited with the virtual password attributes.

Authenticating a user

The authenticate class method determines whether a user name and password is correct:

  User.authenticate('sam', 'secret')

If successful, authenticate returns the user object. Otherwise, it returns nil.

Creating the sessions controller

To allow a user to login and logout, a sessions controller can be created with the needed actions:

  rails g controller sessions new create destroy

While the generate commands creates routes for all three actions, the routes need to be edited in the routes.rb file (config folder) so that create uses the post method and destroy is categorized as a delete operation:

  controller :sessions do
    get 'login' => :new
    post 'login' => :create
    delete 'logout' => :destroy
  end 

Don't forget to remove the old routes! These new routes provide meaningful names for the URL that accesses them. For example, localhost:3000/login will be routed to the new action in the sessions controller. You can test whether the routes are correctly in place by running the command rake routes.

In the sessions controller, only the create and destroy actions require code:

  def create
    if user = User.authenticate(params[:name], params[:password])
      session[:user_id] = user.id
      redirect_to root_path
    else
      @alert = "Invalid user/password combination"
      render 'new'
    end
  end

  def destroy
    session[:user_id] = nil
    redirect_to root_path, :notice => "Logged out"
  end

The create action checks if the user submitted a valid login name and password using the authenticate method. If so, the user_id is set in the session hash. Then, control is redirected to the specified path (you may want it to go somewhere other than the root path). If the name and password is not correct, the controller renders the form again with the alert message.

The destroy action just sets the user_id in the session hash to nil before redirecting to the specified path.

Creating Views for the sessions controller

Only the new action requires any view code (new.html.erb):

  <% if @alert %>
  <p><%= @alert %></p>
  <% end %>
  
  <%= form_tag login_path do %>
  <p>
  <label for="name">Name:</label>
  <%= text_field_tag :name, params[:name] %>
  </p>
  
  <p>
  <label for="password">Password:</label>
  <%= password_field_tag :password, params[:password] %>
  </p>
  
  <%= submit_tag 'Login' %>
  
  <% end %>

Specifying the login_path for the form_tag isn't strictly necessary since the form is automatically submitted to the same path as the form but using the post method.

To logout, the user can click on a Logout link created using the following code:

  <%= link_to 'Logout', logout_path, :method => :delete %>

Limiting Access

A controller can test whether a user is logged in by checking whether session[:user_id] is not nil. Rails filters are typically used for checking login status and limiting access. These details are covered on pages 197 - 200 in the Agile Web Development book.