Login Routine: A 1st Attempt

So I have started to convert my PHP-based issue tracker application to a Rails-based version. When the users of the issue tracker access the site the first thing that they must do is login. This is achieved with a simple form where they supply their username and their password. I was looking at my copy of Agile Web Development with Rails to see how to handle the user login routine. The snippets of code that follow have either been taken directly from this book or have been very heavily influenced by it. Oh, by the way, I’ve tried most of this code but some of it hasn’t been (the user registration bit in particular) so beware. I have asked myself the question, “Why am I spending my time writing this blog entry when I only seem to be copying an example from a book?”. I think the answer is that it has helped me to understand how this user login routine works and it’s helped me come to terms with some of the Ruby / Rails concepts, such as the use of virtual attributes and the use of hash algorithms. I’m only really reproducing snippets of code from the book and not swathes of the actual text (the book is obviously far, far better than explaining than I am!) and the code has been changed my myself a little. Right, back to the code.

I had defined all of the models for the issue tracker app. a little while ago and my User model already had a password field but I wanted to change this to a field named hashed_password, so I created a migration to achieve this. I also created another migration to create a field named salt. Yes they could have been combined into just one migration to achieve this but it just didn’t happen that way.

OK, I’m not 100% sure of what a ‘salt’ is and to be honest I don’t think that I need to know the full story. It seems to simply be a random bit pattern that can be combined with a plain text password to create a more secure password. You can perhaps, if you would like, take a look at the Wikipedia entry for a salt to get further information.

The plain text password can be combined with this salt and then passed through a Secure Hash Algorithm (SHA) to produce a 160-bit hash. This hashed password can then be stored in the database (stored in the string-type attribute named hashed_password); along with the salt value so that we can use it to recreate the hashed version of the password from the password that a user submits when they attempt to login to the site. The SHA that I’ve used is the Rails method named (Digest::SHA1::)hexdigest.

I’ll firstly talk about the code that is used to register a new user first before going on to look at the login procedure.

When our theoretical user first uses the site they will be asked to register as a new user. We will assume that this simply requires them to supply a username and a password. We won’t worry about details such as ensuring that the username is unique in this simplistic example. The password that the user supplies will need to be stored somewhere temporarily while the unencrypted password is given the hash treatment. To achieve this I added a virtual attribute to my existing model. A virtual attribute is basically a model attribute which is not stored in the database. I added this with the following code where getter and setter methods were defined in my model:

# The getter:
def password
 @password
end
# The setter:
def password=(pwd)
 @password = pwd (#1)
 return if pwd.blank?
 create_salt (#2)
 self.hashed_password = User.encrypt_password(pwd, self.salt) (#3)
end

The setter is used by my account controller when the user registers. The code belonging to my registration action that sits within this controller follows:

# Create a new user object:
@user = User.new(params[:user])

# Has post been made and user saved?
if request.post? and @user.save
 flash.now[:notice] = 'You have registered successfully'
 redirect_to :controller => :issues, :action => :list
end

The HTML from my registration view is as follows:

<% form_for :user do |form| %>
 <p>
  <label for="user_name">Enter a username:</label>
  <%= form.text_field :name %>
 </p>
 <p>
  <label for="user_password">Enter a password:</label>
  <%= form.password_field :password %>
 </p>
 <%= image_submit_tag('register.gif', :alt => 'Register') %>
<% end %>

Ok, so I’m still learning but I think that the setter method for my password attribute is probably called when the new User object is created (with the line @user = User.new(params[:user]) in the controller), as the data in the params hash would be used to initialise the User object data. However, being a little new to all of this, I’m not completely sure of this. If I am correct then, once the registration form has been submitted, the password setter function will be called and the plain-text password that the user entered will be passed to it. This password is assigned to an instance variable (#1 in the setter code seen above) and, provided that the password is not completely blank, the create_salt method is called to create a salt for us (#2) and finally the salt is passed to the encrypt_password method along with the plain-text version of the password and the resulting encrypted password is then assigned to the hashed_password model attribute that is finally saved in the database.

Here’s the code for the create_salt method which I added to my User model as a private method:

def create_salt
 self.salt = self.object_id.to_s + rand.to_s
end

Essentially this method takes the id of our user object, converts it to a string, creates a random number, converts this to a string and then joins the two strings before assigning the new string to the salt attribute. The use of the keyword self ensures that the attributes belonging to the current object are used. If self was omitted I believe that the variables would simply be treated as local variables (local to the create_salt method that is). I’ve pretty much taken this method verbatim from the book.

Next is the encrypt_password method which actually takes the plain-text password and the salt that was defined by the create_salt method, concatenates them together along with a bit of extra text and passes them to the hexdigest method:

def self.encrypt_password(password, salt)
 # Join the unencrypted password and the salt:
 value_to_hash = password + 'addtomakemoretricky' + salt

 # Run through the SHA1 digest and return:
 Digest::SHA1::hexdigest(value_to_hash)
end

This method was also defined in my User model as a private method.

Login

Once our user has successfully registered with the site they will need to login when they visit the site in the future. After the user has registered they would ideally be sent an email which gives them a link that will take them to a special first time login page. Once they have logged in for the first time the system would then fully activate the account for that user. This might help to prevent malicious scripts from registering invalid users. I have yet to write the Rails code to achieve however I have done something similar in PHP.

Anyway, on to the login process. Here’s a snippet of code from my view that I used to present the login form to the user:

<% form_tag do %>
 <p>
  <label for="name">Username:</label>
  <%= text_field_tag :name, params[:name] %>
 </p>
 <p>
  <label for="password">Password:</label>
  <%= password_field_tag :password, nil %>
 </p>
 <%= image_submit_tag('login.gif', :alt => 'Login') %>
<% end %>

You can see here that the password_field_tag method is using the password attribute.

Once the user has keyed-in their password and hit the submit button the controller takes over. My controller had the following code:

# Has the form been posted yet?
if request.post?
 # Check if the user details are valid:
 user = User.authenticate(params[:name], params[:password])
 # Valid user object returned?
 if user
  # Store the user's id in a session variable:
  session[:user_id] = user.id
  # Redirect to the main page
  redirect_to :controller => :issues, :action => :list
 else
  flash.now[:notice] = "Invalid username or password, please try again."
 end
end

The first if statement checks that the form has been submitted (otherwise the block is skipped and the login view is shown). I then call my authenticate class method that belongs to my User class. If the authenticate method successfully returns a user object the id of this user is stored in a session and the user is redirected to my main page otherwise, if the authentication fails, the login page is shown again with a flash message.

Right, so the authenticate method takes the user’s username and plain-text password from our form’s params hash, but what happens then? Here’s the code from my model:

def self.authenticate(username, password)
 user = self.find_by_username(username) # (1)
 if user
  if user.salt
   desired_password = encrypt_password(password, user.salt) # (2)
  end

  if user.hashed_password != desired_password # (3)
   user = nil
  end
 end
 # Return the user object
 user
end

Essentially (1) we find the user in the database using the username that was entered at the form, if this username is found we then (2) encrypt the password that was entered at the form using the salt that was originally used for that user (during the registration process) and we then (3) compare the encrypted password that was derived from the login form with the encrypted password that was been stored in the database when the user registered. If they are the same we have not only found the user’s username but we also have the matching password and so we return the user object to the controller (which stores the user id in a session, etc.).

If you’ve read this post and actually found it to be useful in any way then please leave a comment letting me know. I’ve been quite surprised that this blog has had a small trickle of traffic, principally on my post about the use of the select helper.
It’s taken me quite some time to write this particular blog post and I’m a little worried that it contains numerous mistakes and bit of mis-information. I’m going to think twice before I decide to write a post in this level of detail again as I’ve literally taken about a week or so to write the entire post! Yes someone might be searching for help on a particular topic and find their way to this blog but there are going to be far better sites and forums to find more accurate information.

Possibly see a future post for a list of rails web resources that I find useful.

Advertisements

~ by gblake on April 5, 2008.

2 Responses to “Login Routine: A 1st Attempt”

  1. Hi Graham, Nice blog. Something which you may be interested in for future reference is Act_as_authenticated which i use for almost every app i have done in rails. It’s a plugin which is easily installed and built upon and is, i think, invaluble and a real asset to rails.
    Keep up the good work,
    James

  2. Yay! My first blog comment!!

    Cheers James, I’ll have to check it out. I have to admit that I’ve not been doing much work with Rails recently, but hope to get back to it soon.

    G.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: