Caution! - Many of these posts are creepy-old in the Ruby on Rails world (before 1.0!)
The :author => Charles Abbott now blogs here

Initialized! good, Authorized? Great! part 2

2006-03-11   [ 0 comments ]

Ok, on to part 2 of this subject. Since I covered initialization in the last post, I will mainly be revisiting sessions, authentication and authorization. Since I do have an adminsitrative portion to this site (however basic it may be at the moment), I want to add some level of protection to it.

The first thing I would want to do is create some table that holds administrator information (for authentication purposes). So I create a table with id, username and password using the following SQL:

CREATE TABLE `administrators` (
`id` int(11) NOT NULL auto_increment,
`username` varchar(20) NOT NULL default '',
`password` varchar(255) NOT NULL default '',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
);

So with my table created, and my model in place its time to setup a login page (at this point I have also entered some username and password in the table). My login page is relatively simple, it simply contains a form that submits to the admin controller with the username and password I provide. Sparing the HTML details, my login page looks like so:

<%= start_form_tag :action => "authorized?" %>
<%= text_field 'login' , 'username' %>
<%= password_field 'login', 'password' %>
... submit tag, end form tag

Essentially the login page has a form whose target is the 'authorized?' method in my admin controller (the controller my login method is part of). So I should be able to submit my username and password and then have my authorized? method check to see if I am indeed a valid user. If I am, then it should forward me on to the other admin pages (which means my authorized? method needs no corresponding view, since it is simply a gateway to the rest). So what is in my authorized? method:

def authorized?
if Administrator.authenticate(params[:login][:username], params[:login][:password])
session[:admin] = params[:login][:username]
redirect_to :action => "index"
else
flash[:notice] = 'Incorrect Username or Password not Authenticated'
redirect_to :action => "login"
end
end

Since I have my Administrator model (corresponding to my database table), I decided to pass the username and password to it for validation. My model has a simple find(:first, :conditions => [ "username = ? AND password = ?", username, password ]) statement. (Why are the question marks there? that is a RoR feature that escapes the incoming data to prevent the dreaded SQL injection attack!).

If the model finds a row it returns it, thus proving the if to be true which redirects the user to the index method of the admin controller (allowing them to see the secret information). However, if it does not return a row, then the authorized? method stores a message in flash[:message] and redirects back to the login page. Well, everything looks great, users are being authorized before they can see the admin panel. But wait, there is just one problem with this setup:

Anyone can skip the login page and go straight to any other page without reprecussion!

So here comes the really fun part, and one that gave me a bit of trouble (95% caused by my one little oversight, which I will share with you shortly).

Sessions! The way to deal with this in the stateless world of the web is store some data on the server (in my case in a database table) and match it with some cookie you gave the remote machine. Well in the above authenticated? method I had a line that says "session[:admin] ...". That instruction tells Ruby on Rails to store the following information in the always available session variable. So I am already half way there. To enforce that sessions are being checked you need to do two things:

  1. Have some method for checking if the session data exists.
  2. Enforce that method across the other methods/controllers that need protecting

First let us create the method that checks for the session variables, then I'll show you the one-liner that will call it before any action (as you see fit). I decided to call this function 'authenticate' and to throw a simple but effective check in there:

unless session[:admin]
redirect_to :action => "login"
flash[:notice] = 'Session not Active'
return false

Pretty simple, right? If the session isn't there then it quickly redirects the user to the login page, with a nice little message. So here comes the one-liner to call this method each time:

before_filter :authenticate, :except => :login

So, before Rails does anything it is instructed to check the authenticate method for a session (excluding the :login method). Well that's it! You now should have a working authentication system. ..what's that? oh,.. thats right, I was supposed to tell you where I got hung up - and if you tried out the code above you would have experienced the same oversight I had.

Don't forget to :except => [:login, :authorized?]

I wasted several hours wondering why it was authorizing but not authenticating my session - it would have helped if I let it set the session in the authorized? method before I checked for it~!

Thats it for today, next I will post on adding additional security built into this extremely basic authentication system (which has it's flaws). Until then, Happy Coding! =)

:author => "Charles Abbott"
Converting to Ruby on Rails
 

What?

Who?              Link?



Frameworks Good or Bad?   :date => "2007-10-06"
Where is ForTheCode.com Going?   :date => "2007-09-23"
Refactoring - Vital to Software Development   :date => "2007-09-23"
Mongrel Cluster a quick note - and extra notes   :date => "2007-05-20"
Linux Mongrel and Rails   :date => "2007-05-15"
form_remote_tag revisited   :date => "2007-01-07"
How To: Ubuntu 6.10 Edgy on Rails part 3   :date => "2006-12-30"
How To: Ubuntu 6.10 Edgy on Rails part 2   :date => "2006-12-24"
How To: Ubuntu 6.10 Edgy on Rails   :date => "2006-12-22"
verify ... 5.times do cycle   :date => "2006-09-25"
country_select, country_options_for_select, mail_to   :date => "2006-09-05"
Generate and Send Email in Rails   :date => "2006-08-26"
FDF Model, gsub, and send_data   :date => "2006-08-18"
Active Directory Authentication with acts_as_authenticated   :date => "2006-08-17"
Apache2 proxy with Lighttpd - FastCGI for Rails   :date => "2006-08-08"
reverse! && a simple file Upload Class   :date => "2006-07-29"
send_file - a link to download a file   :date => "2006-07-24"
Environments (production, development, test) and cache_pages   :date => "2006-07-04"
.class .methods .instance_variables   :date => "2006-06-14"
select_tag :multiple => true   :date => "2006-06-01"
FileUtils, action_controller rescues   :date => "2006-05-20"
file_field_tag, File.size, File.path, FileUtils.mv   :date => "2006-05-15"
javascript_include_tag, stylesheet_link_tag   :date => "2006-05-02"
submit_to_remote, form_remote_tag, script.aculo.us   :date => "2006-04-30"
periodically_call_remote, simple_format   :date => "2006-04-26"
observe_field - Ajax!   :date => "2006-04-21"
h method, TextHelper, sanitize(), strip_tags()   :date => "2006-04-15"
Rails API :My API [.count(), link_to, text_area :size]   :date => "2006-04-13"
Rails - HTML Select Tag   :date => "2006-04-05"
Pruning Old Sessions   :date => "2006-03-21"
If Elsif Else, and Searching Too!   :date => "2006-03-17"
SHA1 - A quick update   :date => "2006-03-15"
Initialized! good, Authorized? Great! part 2   :date => "2006-03-11"
Initialized! good, Authorized? Great!   :date => "2006-03-08"
Forms and Routing in RoR   :date => "2006-03-06"
My First RoR Post !   :date => "2006-03-05"