Rails: User/Password Authentication from Scratch, Part II

In Part One of this series, I explained how to create a Rails application with a sign up form. This article concludes the process of user authentication

Authentication Method

As we can now save encrypted passwords in the database, it’s time to setup the authentication method that will take a username/email and password to find out if that matches a user in the database. We need a query to match username/email and, if found, encrypt the entered password and compare it with the encrypted password in the database.

Let’s write an authentication method in the user model

We see that if login_password matches, we return the user object. Otherwise, the method returns false.


Sessions Controller and Login form template

With our authentication method in place, let’s generate the sessions controller. The SessionsController will have create login, home, profile and setting actions.

Let’s focus on the login template. The act of logging in consists of a form that accepts username/email and password, passing the values to the login_attempt action.


Creating `login_attempt` Action

The login_attempt action accepts params from the login form and passes it to the authentication method we created previously. If the user is found, we redirect to the home action. Otherwise, we’ll render the login template again.

There is something missing here. We need to save the logged-in state of the user. If we don’t, we’ll have to authenticate before each requested action, which is not DRY and can be expensive. We’ll use the session to keep track of the user state, checking it before every request from the user.

Let’s see how that is done.


Cookies, Sessions and Super-Cookies

Cookies:

We know that when a user sends a request to the web server, it is treated as a new request. The web server is stateless, meaning, it doesn’t know or care about previous requests. A simple solution to passing state between requests is cookies. The web server sends data in a cookie file to the browser, which saves it and sends the cookie data back with each request.

Using cookies in Rails is a snap:

Sessions:

However, due to some limitations of cookies, such as 4kb max size and the fact that they can be read, altered or deleted, we will use sessions. Using sessions, the web server sends an ID in the cookie file to the browser. The ID is sent back on each request and is used to pull data out of the session, which is stored on the server.

Using sessions in Rails is, like cookies, simple:

Super-Cookie:

Writing the session information to persistant storage, such as a file or database, doesn’t scale well. Instead, we will use cookie storage, which is very fast and secure. This so-called super-cookie is encrypted and placed in the session, ensuring that the user can not read or alter it.

Session Configuration:Inside the config/initializers folder, you’ll find two configuration files for sessions. The first is session_store.rb, which is used to configure the storage option you want to use ( cookie_store is the default option.) The second is secret_token.rb, which contains the string that Rails use to encrypt the cookie file.

Now let’s get back to our application and save the login state in the session if the user is authorized. Back in the login_attempt action

Here, we’ve created a user_id session key, storing the authorized user id. This will be retrieved on subsequent requests.

Note:It is a good practice to always store the ID that refers to the object in the session file, not the object itself. The reasoning here is, simply, an ID value is small and a whole user object is much larger. The ID can be used to retrieve the user, if needed.

“Always store the ID that refers to the object in the session file, not the object itself”


Access Restriction

We need to check the session file every time the user requests some protected action. To do this, we are going use the before_filter method.

before_filter is a method to perform a function before the specific action is executed. It smells a lot like callbacks, but filters are for controllers while callbacks are for models.

The before_filter method takes the name of the method to run before the action. The second parameter is a list of actions that we want to filter. Let’s add it to our application.

In the ApplicationController, which is the superclass of all our controllers, add:

The authenticate_user method checks if the user_id session is available. If so, it assigns the user object to the @current_user instance variable and returns true, allowing the action to be executed. If not, return false and redirect the to the login page. The other method, called save_login_state, prevents the user from accessing the signup and login pages whilst logged in.

Add the before_filter calls to SessionsController

and to UsersController

The @current_user value can be used in the templates, now, to present information about the logged in user.


Logout

Our cycle would be incomplete without some way to log out of the session. Logging out, as I mentioned before, clears out the session variable and redirects to the login page.


Routes Configuration

Finally, before you can test your code, edit your routes file as follows:

Now, run your server, login, navigate to other actions, and logout. Pretty sweet, eh?


Final Thoughts

You’ve just learned how to implement password-based authentication in your app from scratch. Of course, there are existing libraries that handle authentication for Rails, peforming the tasks shown in this article for you.

Let’s take a quick look on the most common libraries:

  • Devise: Devise is the most common library in Rails used for authentication with a 24.874 popularity rating according to the ruby-toolbox website. It is a flexible authentication solution based on Warden. It is Rack based and a complete MVC solution.
  • Authlogic: Authlogic introduces a new type of model, allowing multiple authenticated models (as opposed to just a User model) It is a clean and simple ruby authentication solution, occupying second place in popularity rating with 12.815.
  • OmniAuth: OmniAuth is a provider-based authentication framework for web applications. It is powerful and flexible, allowing the developer to do as little as possible by leveraging other authentication mechanisms (such as Facebook or Twitter) It’s popularity rating is 11.128

There are more libraries for handling password-based authentication, which you can review here.


Conclusion:

In this tutorial, we covered the entire process of implementing simple, user and password-based authentication for your Rails application. We learned how to create a new user, encrypt the password before saving, and how to authenticate the user by utilizing the session. Finally, we created the logout action, which clears the session file and redirects the user to login page. Now you’ve an overall idea about how to create user authentication in Rails from scratch.

Thanks for reading, I hope it was useful and enjoyable.


Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • David Jenkins

    Thank you! I’m always amazed how hard it is to find a good tutorial for authentication on the web. You have answered a need!

  • http://www.opinionatedprogrammer.com/ Jo Liss

    Just a heads-up for when you have to deal with hashes yourself:

    > encrypted_password == BCrypt::Engine.hash_secret(login_password, salt)

    I’m not sure if it’s exploitable here, but in general these kinds of equality checks of hash strings leave you vulnerable to timing attacks.

  • Huy Nguyen

    Just a heads up, the Authlogic link points to devise.

  • Ahmed Fouad

    Very good tutorial need one for creation a registration form

  • Raymond

    Im not sure what’s being done here, are the parameters being set to an empty string? :
    def self.authenticate(username_or_email=””, login_password=””)

  • Jamo

    Yep those are set to an empty string unless those params are provided in that method call. That’s how you won’t get an error due using nil as one params

  • http://virvit.ru VirVit

    Thank you a lot! Very helpfull and clear! I hope to see new tutorials from you.

  • Brendan

    Excellent article. Thanks!

    Note to others: I couldn’t use this code verbatim. I had to add a couple routes and tweak the User object slightly (as per the previous article) but the important bits are here.