A Basic User Authentication Model in Rails 4

By @zachfeldman
Written on Mar 13, 2014

Most developers already understand how user authentication works and just want to integrate it into their app straight away. However, going with a package like Devise or using Rails’ built-in hassecurepassword method as someone who is just getting started in understanding the back end will severely limit how much you know about how user authentication really works.

This tutorial will walk you through the steps of rolling your own user authentication model in a Rails 4 web application.

Start with Home

Once you’ve generated a new Rails app with rails new appname, you’ll want to start by generating a simple static page to serve as the home page of your new web application.

Add this line to your routes.rb file, which is located in the config directory:

1
get '/' => 'home#index'

Next, create a file called home_controller.rb inside of app/controllers with the following code:

1
2
3
4
class HomeController < ApplicationController def index end end

Finally, create the home page view file itself in app/views/home:

1
<h1>Welcome to my Website</h1>

Boot up the Rails server in the Terminal by changing to your project directory and typing rails server. When you hit your website in the browser at localhost:3000, you should see your Welcome to my Website page.

A User Model

We’ll need a model to store all of the cool user information that we’re going to need to sign people in. Let’s use a Rails generator for this instead of writing our own custom migration. In your project directory, type this into the Terminal:

1
rails generate model User email:string password_hash:string password_salt:string

Make sure you migrate the database so your users table is actually created:

1
rake db:migrate

Now we should have a dependable User model to work with.

What is Password Hashing?

This is where things really get juicy! Remember when people found out that HostMonster stored passwords in plaintext and got pretty angry about it? That’s for good reason. If a hacker gains access to your database and your passwords are stored as plain text, then a few different security concerns come to mind. Besides the hacker being able to log into any user account on your website, many users use the same passwords across services. It’s only a matter of time before someone takes that database dump and runs it against any other services. Once an e-mail address is compromised with a stolen password, your users could be in a very bad place.

Instead, the best option you have is to hash your passwords before they’re stored in the database. What does this mean? Using an encryption algorithm program that takes a unique password (supplied by the user) and a unique password salt (a random string of characters), you’ll generate a password hash to store in your database instead of storing a plain text password. When a user attempts to login, their stored salt and the supplied password are run throug the algorithm once again and compared to the stored hash. If they match, the user has been authenticated.

password-hash-1

The password hashing process

Source: unixwiz.net

password-hash-2

The user login process using a hashed password

Source: unixwiz.net

“But oh no,” you say, “I have no idea how to construct a secure password hashing algorithm!” Open source comes to the rescue once again with the bcrypt-ruby gem, an amazing password hashing utility designed by the OpenBSD project.

Add the following line to your Gemfile:

1
gem 'bcrypt-ruby', '~> 3.1.5', require: "bcrypt"

Then bundle install in your project directory to install the gem.

Hashing Passwords with BCrypt

The best way to implement the password hashing and then authentication with password hashing process is to put methods on the User model. This way, a User object has the ability to encrypt and decrypt itself. In your app/models/user.rb file, let’s start constructing the methods:

1
2
3
4
5
6
7
8
9
class User < ActiveRecord::Base def encrypt_password end def self.authenticate(email, password) end end

What do we need inside of our encryptpassword method? Let’s lay it out point by point:

  1. A random password salt is generated for this specific user and stored on this specific user
  2. The password salt and supplied password for this user are hashed together into a password hash, which is also stored on this specific user
  3. This all should happen before the User record is saved
“``ruby def encryptpassword self.passwordsalt = BCrypt::Engine.generatesalt self.passwordhash = BCrypt::Engine.hashsecret(password, password_salt) end ”“

Great, that takes care of items 1 and 2. The only problem is that our User model does not actually have a password column in the database. In order to persist one in memory long enough to generate a password hash, we need to allow password as an attribute on User, even if only temporarily.

1
attr_accessor :password

The above should go at the top of our User class. What this means is that a new instance of User can be created with all of the data from our sign up form and we can store a password on this instance without having a database column back it. Sometimes this is known as a "virtual attribute”.

We’ll also want a password confirmation field so we can be sure our users are entering the correct password. Add this line to create another “virtual attribute” for password_confirmation that also does not exist in the database:

1
validates_confirmation_of :password

Finally, we’ll need this method to run before any new instance of User is saved. We can use the ActiveRecord before_save hook for this:

1
2
3
4
5
6
7
8
9
class User < ActiveRecord::Base attr_accessor :password before_save :encrypt_password def encrypt_password self.password_salt = BCrypt::Engine.generate_salt self.password_hash = BCrypt::Engine.hash_secret(password, password_salt) end end

With the above code, we should be able to create a new instance of User and have their password hashed and a salt generated. Try it out by running rails console in the Terminal and then the following command:

1
User.create(email: "john@john.com", password: "badpassword1234")

That should create a new User that gets their password salted and hashed, then saved in the database. The last line of what is returned when you run the command should look something like this:

1
=> #<User id: 1, email: "john@john.com", password_hash: "$2a$10$bZO4byWczN8w8G02WzQK9eNeYrA8tZ0ZIkGtNPpbye", password_salt: "$2a$10$bZO4byWczN8w8G02WzQK9e", created_at: "2014-10-20 15:33:18", updated_at: "2014-10-20 15:33:18">

The Sign Up Process

Now that we have password hashing setup for newly created users, we need to create a process to sign them up on the front and back end. We’ll need a new controller in our controllers folder for actions based around the User resource called…UsersController! We’ll also need a create and new action on this controller. The new action will be for the sign up form view while the create action will process the form data submitted.

1
2
3
4
5
6
7
class UsersController < ApplicationController def new end def create end end

Let’s hookup the routes we’ll need in our routes.rb file. This can be really quickly done using the resources keyword, which will outfit our users resource with all the usual CRUD (create, read, update, destroy) routes:

1
2
3
Rails.application.routes.draw do resources :users end

If we run the $ rake routes command from the Terminal, we can see what routes this provides us with:

1
rake routes

Screen Shot 2014-03-12 at 4.36.13 PM

Most important here are the routes that map to the UsersController’s create and new actions. When the /users/new URL is hit, our new action in the UsersController is loaded. Right now it has nothing in it, so by default Rails will attempt to load a view in the app/views/users folder called new.html.erb. We’re going to need that file! Inside of it, we’ll put our signup form:

1
2
3
4
5
6
7
8
9
10
<h1>Sign Up</h1> <%=form_for User.new do |f| %> <%=f.label :email %> <%=f.text_field :email %> <%=f.label :password %> <%=f.password_field :password %> <%=f.label :password_confirmation %> <%=f.password_field :password_confirmation %> <%=f.submit "Sign Up" %> <%end%>

Now when we restart our app and access /users/new, we should see the above signup form. All that’s left is to process the form data inside of our UsersController. To reiterate, when the signup form gets submitted, that data is processed inside of the create action of the UsersController. That action should attempt to create a new user given the data supplied, like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class UsersController < ApplicationController def create @user = User.new(user_params) if @user.save flash[:notice] = "Welcome to the site!" redirect_to "/" else flash[:alert] = "There was a problem creating your account. Please try again." redirect_to :back end end private def user_params params.require(:user).permit(:email, :password, :password_confirmation) end end

At the end of our controller, we need to put a userparams method because of the new strong parameters method of protecting database attributes at the controller level rather than the model level. To read more about why this is necessary, check out <a href=“http://weblog.rubyonrails.org/2012/3/21/strong-parameters/” target=“new”>this article.

Remember that we took care of all the password hashing behind the scenes in our User model, so when the user is saved, their password is hashed automagically.

The Authentication Process

We forgot to fill out one of the must important methods - our authenticate method on the User model! Here’s what we’d like this method to do:
  1. Given an email and password
  2. Lookup a user in the database
  3. If a user is found
  4. Hash the password with that user’s salt using the same bcrypt method we used originally to hash the user’s password
  5. If the hashed password matches with what is stored in the database, return the user’s object
  6. If it does not match, return nil
The following code should work nicely to accomplish that, right inside of our user.rb model file:

1
2
3
4
5
6
7
8
def self.authenticate(email, password) user = User.where(email: email).first if user &amp;&amp; user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt) user else nil end end

Because this is a class method, it can be called directly on the User class. So if we want to authenticate a User and then assign them to a variable called current_user if they’ve been authenticated correctly, we could use the following code:

1
current_user = User.authenticate("john@john.com", "badpassword1234")

Try the above line out in the (restarted) rails console to make sure it works.

Signing In A User

Now that we can authenticate a user using a simple method, it’s time to create the sign in process on the front end of our web application.

Create a new controller called sessions_controller.rb. You’re only going to need 3 actions for this controller: new, create, and destroy.

You’ll also need 3 new routes in your routes.rb file:

1
2
3
get "/log-in" => "sessions#new" post "/log-in" => "sessions#create" get "/log-out" => "sessions#destroy", as: :log_out

The first route is for the login form, the second to process the data from that form, and the third to sign users out.

The new route won’t have anything in it really since we just need it to render the log in page at /log-in. Speaking of that page, let’s add a view inside of app/views/sessions/ called new.html.erb:

1
2
3
4
5
6
7
8
<h1>Log In</h1> <%=form_tag log_in_path do%> <%=label_tag :email %> <%=text_field_tag :email %> <%=label_tag :password %> <%=password_field_tag :password %> <%=submit_tag "Log in" %> <%end%>

Notice the use of loginpath, which is a Rails helper way of saying we’d like this form to submit to the route /log-in. Remember that we aliased this route with the name log_in using the as: attribute in our routes.rb file above.

Boot up your Rails server and try hitting the /log-in route at localhost:3000/log-in.

Processing User Sign In Data

The next step in signing in our dear user is to process their login information, authenticate them, and then actually log them in. This is where the session store comes in.

In Rails the session hash can store any data you’d like to persist for a user’s session. A session in basic computer science terms is just a moment in time when two devices are communicating and exchanging information. As it pertains to your website, a session is where any data is stored about a specific user accessing your web application.

As mentioned, you can store anything you’d like in this session hash like so:

1
session[:some_info] = "Hello"

If I were to put the above inside of one of our controller actions and then I called it again in another (session[:some_info]), the data would be persisted between the two actions. Any other action from that moment on would allow me to access the data I just stored inside this session hash.

In order to log in a User, we persist their id in the session hash if their username and password are correct. So the pseudo-code we need to process a user login should go like so:

  1. Look up the user in the database by their email address
  2. If they exist in the database, hash the password they submitted using the salt stored on that specific user
  3. If the hash created matches the one stored on that specific user, the user has been authenticated
  4. Store that user’s id in the session hash using the [:user_id] key so we know which user is logged in at various points in our app’s views
Hey, remember that we already wrote the code to authenticate a User on the User model? That makes things easy! The code we need inside of our create action in the SessionsController should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def create #try to authenticate the user - if they authenticate successfully, an instance of the User model is returned @user = User.authenticate(params[:email], params[:password]) #if an instance is returned and @user is not nil... if @user #let the user know they've been logged in with a flash message flash[:notice] = "You've been logged in." #THIS IS THE MOST IMPORTANT PART. Actually log the user in by storing their ID in the session hash with the [:user_id] key! session[:user_id] = @user.id #then redirect them to the homepage redirect_to "/" else #whoops, either the user wasn't in the database or their password is incorrect, so let them know, then redirect them back to the log in page flash[:alert] = "There was a problem logging you in." redirect_to log_in_path end end

Logging People Out

The final action we’ll need is the destroy action on the SessionsController. This action will destroy a user’s session, effectively logging them out. How do we “destroy a user’s session”? Just set the session[:user_id] to nil!

1
2
3
4
5
def destroy session[:user_id] = nil flash[:notice] = "You've been logged out successfully." redirect_to "/" end

Special Bonus: The current_user Method

Wouldn’t it be nice if, across all of our controllers and views, we could have a method to return an instance of User with the information of the currently logged in user? Try sticking this code in your application_controller.rb file:

1
2
3
4
5
def current_user @current_user ||= User.find(session[:user_id]) if session[:user_id] end helper_method :current_user

Basically, if session[:userid] is set, find the User with that id. The @currentuser ||= part of the method will cache the current user inside of the @current_user instance variable so we don’t have to make a database call each time the method is called.

Wrap Up

Although it’s not really done so much anymore, rolling your own authentication in Rails is really important. Going through the process will give you a much better understanding of the login process used by Devise and hassecurepassword.

Here’s a GitHub repo with a Rails app that has this basic user auth scheme implemented: https://github.com/nycda/scratch-auth

If this article interested you and you want to learn more about Rails, check out our Ruby on Rails 101 evening class or our Web Development Intensive.

X-posted from the New York Code + Design Academy blog.





Sign up for our e-mail list to hear about new posts.