Recipe 4.16. Using Filters for Authentication


Problem

You want to authenticate users before they're allowed to use certain areas of your application; you wish to redirect unauthenticated users to a login page. Furthermore, you want to remember the page that the user requested and, if authentication succeeds, redirect them to that page once they've authenticated. Finally, once a user has logged in, you want to remember his credentials and let him move around the site without having to re-authenticate.

Solution

Implement an authentication system, and apply it to selected controller actions using before_filter.

First, create a user database to store user account information and login credentials. Always store passwords as hashed strings in your database, in case your server is compromised.

db/migrate/001_create_users.rb:

class CreateUsers < ActiveRecord::Migration   def self.up     create_table :users do |t|       t.column :first_name,      :string       t.column :last_name,       :string       t.column :username,        :string       t.column :hashed_password, :string     end     User.create :first_name => 'Rob',                 :last_name => 'Orisni',                 :username => 'rorsini',                 :hashed_password =>                         '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8'   end   def self.down     drop_table :users   end end

In your ApplicationController, define an authenticate method that checks if a user is logged in and stores the URL of the page the user initially requested:

app/controllers/application.rb:

# Filters added to this controller will be run for all controllers in the # application. # Likewise, all the methods added will be available for all controllers. class ApplicationController < ActionController::Base   def authenticate     if session['user'].nil?       session['initial_uri'] = request.request_uri       redirect_to :controller => "users", :action => "login"      end   end end

To make sure the authenticate method is invoked, pass the symbol :authenticate to before_filter in each controller that gives access to pages requiring authentication. Here's how to make sure that users are authenticated before they can access anything governed by the ArticlesController or the BooksController:

app/controllers/articles_controller.rb:

class ArticlesController < ApplicationController   before_filter :authenticate   def admin   end end

app/controllers/books_controller.rb:

class BooksController < ApplicationController   before_filter :authenticate   def admin   end end

Now, create a login form template to collect user credentials:

app/views/users/login.rhtml:

<% if flash['notice'] %>   <p style="color: red;"><%= flash['notice'] %></p> <% end %> <% form_tag :action => 'verify' do %>   <p><label for="user_username">Username</label>;   <%= text_field 'user', 'username'  %></p>     <p><label for="user_hashed_password">Password</label>;   <%= password_field 'user', 'hashed_password'  %></p>     <%= submit_tag "Login" %> <% end %>

The User sController defines login, verify, and logout methods to handle the authentication of new users:

app/controllers/users_controller.rb:

class UsersController < ApplicationController   def login   end   def verify     hash_pass = Digest::SHA1.hexdigest(params[:user][:hashed_password])[0..39]     user = User.find(:first,:conditions =>                        ["username = ? and hashed_password = ?",                         params[:user][:username], hash_pass ])     if user       session['user'] = user       redirect_to session['initial_uri']     else           flash['notice'] = "Bad username/password!"        redirect_to :controller => "users", :action => "login"      end        end   def logout     reset_session     # Redirect users to Books#admin, which in turn sends them to      # Users#login, with a refering url of Books#admin:     redirect_to :controller => "books", :action => "admin"    end end

Next, provide a mechanism for users to log themselves out if they're not comfortable letting their session time out on its own. Create a "logout" link with a named route using logout_url:

app/views/articles/admin.rhtml:

<h1>Articles Admin</h1> <%= link_to "logout", :logout_url  %>

app/views/books/admin.rhtml:

<h1>Books Admin</h1> <%= link_to "logout", :logout_url  %>

Finally, define the "logout " named route with its URL mapping:

config/routes.rb:

ActionController::Routing::Routes.draw do |map|   map.logout '/logout', :controller => "users", :action => "logout"       # Install the default route as the lowest priority.   map.connect ':controller/:action/:id' end

Discussion

Adding authentication to a site is one of the most common tasks in web development. Almost any site that does anything meaningful requires some level of security, or at least a way to differentiate between site visitors.

The Rails before_filter lends itself perfectly to the task of access control by invoking an authentication method just before controller actions are executed. Code that is declared as a filter with before_filter has access to all the same objects as the controller, including the request and response objects, and the params and session hashes.

The solution places the authenticate filter in the Book and Article controllers. Every request to either controller first executes the code in authenticate. This code checks for the existence of a user object in the session hash, under the key of user. If that session key is empty, the URL of the request is stored in its own session key, and the request is redirected to the login method of the User controller.

The login form submits the username and password to the Login controller, which looks for a match in the database. If a user is found with that username and a matching hashed password, the request is redirected to the URL that was stored in the session earlier.

When the user wishes to log out, the logout action of the User controller calls reset_session, clearing out all the objects stored in the session. The user is then redirected to the login screen.

See Also

  • Section 14.4"




Rails Cookbook
Rails Cookbook (Cookbooks (OReilly))
ISBN: 0596527314
EAN: 2147483647
Year: 2007
Pages: 250
Authors: Rob Orsini

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net