Ruby on Rails: How to require login for some or all actions

Although much of your site may be public, you may want to keep parts of it only for users who have logged in. Specifically, you may want to keep certain changes behind a login screen.

Preparation

No special preparation is required.

Database

You will require a database table for registered users with the following schema:

Column Type
id Integer, Not NULL, Auto Increment
name VARCHAR(16) (or any other maximum size of user name)
password VARCHAR(64)

User Model

The user model requires Digest, which provides an MD5 hashing method by which to hash the password:

require "digest"

class User < ActiveRecord::Base
  def cleartext=(rhs)
    self.password = Digest::MD5.hexdigest(rhs)
  end

  def cleartext
    return ""
  end

  def self.authenticate(name, password)
    find(:first, :conditions => ["name = ? AND password = ?", name, Digest::MD5.hexdigest(password)])
  end
end

User View

In the form where the user is created or edited, cleartext should be used in place of password:

  <label for="user_cleartext">Password:</label>
  <%= f.text_field('cleartext', :size => 32) %>

User Controller

Once the above changes are made to the user model and view, no changes to the controller are required.

Other Models

No changes are required to other models.

Other Views

You will require a login page. The page should be named index.html for consistency with the login controller below. The page should include a form submitting to the authenticate action in the login controller:

<% form_tag 'login/authenticate' do %>

  <label for="login_name">Name:</label>
  <%= text_field_tag('name') %>

  <label for="login_password">Password:</label>
  <%= password_field_tag('password') %>

  <%= submit_tag "Log In" %>

<% end %>

Other Controllers

Your application controller should include the following method. It checks whether a valid session exists. If not, it stores the requested page in the session, and redirects to the login controller.

  protected

  def authenticate
    unless (nil != session && nil != session[:user])
      session[:return_to] = request.request_uri
      redirect_to :controller => "login"
      return false
    end
  end

The login controller’s index method will merely display the login page. Submission of the login form will call the authenticate action. If the user is successfully authenticated, then this action redirects to the page requested before authentication.

class LoginController < ActionController::Base
  def index
  end

  def authenticate
    if session[:user] = User.authenticate(params["name"], params["password"])
      if session[:return_to]
        redirect_to session[:return_to]
        session[:return_to] = nil
      else
        redirect_to :controller => "summaries"
      end
    else
      flash[:alert] = "Login failed."
      redirect_to :action => "index"
    end
  end

  def logout
    reset_session
    flash[:alert] = "Logged out."
    redirect_to :action => "index"
  end
end

For controllers that you want protected behind a login page, add the following line:

before_filter :authenticate, :only => [:new, :edit, :update, :create, :destroy]

This prevents public access to controller methods that write to the database. To prevent access to all methods, omit the :only parameter.

Tags: Technical

Created at: 1 December 2008 12:12 AM

NO COMMENTS ALLOWED