Controlling Information Access with the Rails Action Controller - 4.16 Using Filters for Authentication (Page 5 of 5 )
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
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 %>
The UsersController 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:
# 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.
DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware.