This is the first part of a two part post talking about how to implement authentication and authorization from scratch. Throughout the post I try to mention concepts that a new ruby developer might find useful. If you have any questions or if you would like me to cover certain parts in more detail leave me a comment or shoot me an email. Enjoy!
Here's the link to the github repository if you just want to see the code.
Here's a quick explanation of the difference between the two concepts. An application needs to know who you are in order to know what you can do. Authentication tells your application who you are while authorization tells the application what you can do.
So let's get started by creating a new rails application:
rails new auth2 cd auth2
Next let's generate a rails model
rails g model user email password_hash password_salt
We can see that Rails generated a user model for us. You already know what the email address is for but you may be wondering why we need the password_hash and the password_salt attributes. So here's a quick explanation:
Passwords in general are vulnerable to brute force. With enough computing power, somebody could guess a user's password. We want to make this guessing game as difficult as possible. For this reason we use a hashing algorithm to encrypt passwords. For example: the encrypted string
'secret_password' might look something like this:
We now know the reason for hashing passwords but what about that strange salt attribute? Even with a hashing algorithm, if our users are highly creative and choose the very unique '123456' as their password, it will be relatively easy for somebody to use a rainbow table to reverse engineer the hash and guess a user's password. That being said, people in general tend to use common passwords. That's why a salt comes in handy. So what is a salt? A salt is some randomly generated string that we can use to concatenate with the password and create another stronger password that would be more difficult to guess.
It is beyond the purpose of this post to go into more details about how brute force can guess a user's password. If you would like to know more here's a link that talks about rainbow tables. Also if you would like to know some other very 'original' passwords check this out.
Equipped with this fresh understanding let's see how to implement it in ruby. For the purpose of this tutorial using the ruby digest library is more than enough. However if you expect to use this implementation in a production application with lots and lots of users I recommend you take a look at the bcrypt-ruby gem as well.
We already have a user model with the following attributes:
email, password_hash, password_salt. Since the user is not supposed to interact directly with the
password_salt, make sure they are not accesible in your model.
You should have the following line in your user model:
Next we realize that when a user signs up he needs to enter a
password and potentially a
password_confirmation. So let's make these attributes also acessible.
attr_accessible :email, :password, :password_confirmation
Now both password and password_confirmation are accessible but they are not real Active Record attributes. This means we need to define getter and setter methods for them. We can do that by adding the following line of code:
attr_accessor :password, :password_confirmation
Next let's create the encryption mechanism. Your user model should look something like this:
class User < ActiveRecord::Base attr_accessible :email, :password, :password_confirmation attr_accessor :password, :password_confirmation before_save :encrypt_password private def encrypt_password if password.present? self.password_salt = generate_salt self.password_hash = generate_hash(password, password_salt) end end def generate_hash(password, password_salt) Digest::SHA2.hexdigest(password + password_salt) end def generate_salt Digest::SHA2.hexdigest(Time.now.to_s + Random.rand.to_s) end end
We have an encrypt_password method that is called in a before_save filter. The salt is generated from the current time and a random number. We also need the generate_hash method which will take a hash and the password provided by the user and create a stronger password.
We know how the password will be encrypted in the database but the user doesn't have a way to sign up yet. Let's go ahead and implement that.
Add the following line to your routes file:
get 'sign_up' => 'users#new'
As you might know this line of code tells your application to grab a URL that looks like this: http://example.com/sign_up and map it to the
UsersController and the
new action. For more information on routing checkout the rails guides.
We don't have that controller yet so let's go ahead and create one.
class UsersController < ApplicationController def new @user = User.new end end
This new action looks relatively simple. Be aware though that there is a lot going on behind the curtains in the Action Pack part of Rails. If you would like to know more about that drop me a message and I will plan for a future blogpost.
Let's go ahead and create the view our controller is expecting:
<%= form_for @user do |f| %> <%= f.text_field :email, placeholder: 'email' %> <%= f.password_field :password, placeholder: 'password' %> <%= f.password_field :password_confirmation, placeholder: 'password confirmation' %> <%= f.submit 'Sign up' %> <% end %>
If you are not familiar with some of the rails methods such as
text_field make sure you check the rails api for a quick explanation. Also take a look at the html code generated in the source file. For your convenience I'll post a copy below, accompanied by a short explanation and removing some noise from the code :
<form action="/users" class="new_user" id="new_user" method="post"> <input id="user_email" name="user[email]" placeholder="email" size="30" type="text" /> <input id="user_password" name="user[password]" type="password" /> <input id="user_password_confirmation" name="user[password_confirmation]" type="password" /> <input name="commit" type="submit" value="Sign up" /> </form>
Let's take a closer look at this html that Rails generated for us. You can see within the opening form tag there is an
action html attribute containing the '/users' string. This is the path we need to configure so that Rails knows where to send the request. We also notice that the
method attribute contains the POST HTTP request. This is another Rails convention and if you are not familiar with it you should check out the rails gudies.
In your config folder in your routes file add the following line of code:
post 'users' => 'users#create'
Now our form is hooked to the create method from the UsersController so let's go ahead and build the create method:
def create @user = User.new(params[:user]) if @user.save session[:user_id] = @user.id redirect_to root_url, notice: 'Signed up!' else render 'new' end end
Now we have a way to create new users based on the parameters they provide. You may also have noticed that I added
session[:user_id] = @user.id. We do this to improve a user's experience. We don't want them to have to authenticate again once they signed up. By the way we don't yet have a way for users to authenticate. Let's go ahead and implement that as well.
First create the sign in view.
<%= form_tag('/sessions') do %> <%= text_field_tag :email, nil, placeholder: 'email' %> <%= password_field_tag :password, nil, placeholder: 'password' %> <%= submit_tag 'Sign in' %> <% end %>
Next let's create the Sessions Controller:
class SessionsController < ApplicationController def new end def create user = User.with_email(params[:email]) if user && user.authenticated?(params[:password]) session[:user_id] = user.id redirect_to root_url, notice: 'Signed in!' else flash.now.alert = 'Email and password are not valid' render 'new' end end end
We have a new method that is originally empty but has inherited functionality. We also have a create method that looks for a user with a particular email. If the user provides the correct information we set the
session[:user_id] equal to the users id.
Our users can now sign up and sign in. They don't have yet a way to sign out. Let's go ahead and quickly implement this last piece as well.
Place this line of code in your routes:
get 'sign_out' => 'sessions#destroy'
Now create the destroy method in the sessions controller.
def destroy session[:user_id] = nil redirect_to root_url, notice: 'Signed out!' end
Almost done. You may also have noticed that we used flash to record alerts or notices. We did that so we can make it visible if we are signed in or signed out. Drop this code in:
<% if current_user %> <span> Signed in as <%= current_user.email %> | <%= link_to 'Sign out', sign_out_path %> </span> <% else %> <span> <%= link_to 'Sign in', sign_in_path %> or <%= link_to 'Sing up', sign_up_path %> </span> <% end %> <% flash.each do |name, msg| %> <div><%= msg %></div> <% end %>
Also drop this code in your:
def current_user @current_user ||= User.find_by_id(session[:user_id]) end helper_method :current_user
helper_method is a nice way to make instance variables available in your views. Try to make use of it when possible.
There you have it. Users can now sign up, sign in and sign out. This is a very basic authentication system. Much more can go into this authentication process but this should give you a basic understanding of what happens behind an authentication system.
Remember that you can also fork the source code and play with it at github/mlpinit/auth2. If you have any questions ask me and I will do my best to help you out!