How To Build Your Own Authentication in Rails 5
Building your own authentication in a Ruby on Rails application is easy using existing components in Rails. You could use existing gems like Devise, which are awesome, but in some cases they do too much, and are hard to work with if you want to do any modifications to conventional login behaviors. Rails already has built in authentication called
has_secure_password, which is a module for Active Model classes that will take care of most of the hard work our authentication will require.
To demonstrate this, we'll create a new Ruby on Rails application:
$ rails new sample_app $ cd sample_app
In the the
Gemfile, uncomment or add the following gem which handles encryption:
gem 'bcrypt', '~> 3.1.7'
Then run bundle:
We will then create a controller to represent our application to a logged-in user. We'll generate these base files and edit them later:
$ rails generate controller Home index
Users are who we are authenticating. To login to our application, they will need an email and password, but for
has_secure_password, the only requirement on the User model is to have a
password_digest field. Additional validations will be added to the class:
- Password must be present on creation
- Password length should be less than or equal to 72 bytes
- Confirmation of password (using a password_confirmation attribute) (OPTIONAL)
Let's create the model by running the Rails scaffold generator, which will additionally add the forms, and views we'll use.
$ rails generate scaffold User email:uniq password:digest $ rake db:migrate
password:digest will add
has_secure_password to our model, as well as add the extra attributes for
password_confirmation where needed in our forms and views.
user.rb we'll add validations so that emails are unique (although we did add unique to our database, so an error will be raised regardless), but this will raise an error for our form and inform the user that their email is not unique:
class User < ApplicationRecord has_secure_password validates :email, uniqueness: true end
We can also see the automatically generated
views/users/new.html.erb (from our scaffold command above) our new User form:
<%= form_with(model: user, local: true) do |form| %> <% if user.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2> <ul> <% user.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= form.label :email %> <%= form.text_field :email %> </div> <div class="field"> <%= form.label :password %> <%= form.password_field :password %> </div> <div class="field"> <%= form.label :password_confirmation %> <%= form.password_field :password_confirmation %> </div> <div class="actions"> <%= form.submit %> </div> <% end %>
Password confirmation is optional. We have included
password_confirmation in our form, which will be used and validated for, but if we were to exclude it, then no confirmation would be needed.
We now create the Sessions controller and views to handle sessions.
$ rails generate controller sessions new create destroy
For logins, we need to the edit the new session login form
<p><%= alert %></p> <h1>Login</h1> <%= form_tag sessions_path do |form| %> <div class=”field”> <%= label_tag :email %> <%= text_field_tag :email %> </div> <div class=”field”> <%= label_tag :password %> <%= password_field_tag :password %> </div> <div class=”actions”> <%= submit_tag %> </div> <% end %> <p> Or, <%= link_to "Sign Up", signup_path %> </p>
We need to update our Sessions controller
class SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:email]) if user && user.authenticate(params[:password]) session[:user_id] = user.id redirect_to root_url, notice: "You are logged in." else flash.now[:alert] = "Your email or password is invalid" render :new end end def destroy session[:user_id] = nil redirect_to root_url, notice: "You are logged out!" end end
We need to set the routes for handling logins and logouts:
Rails.application.routes.draw do root to: "home#index" resources :sessions, only: [:new, :create, :destroy] resources :users get "signup", to: "users#new", as: "signup" get "login", to: "sessions#new", as: "login" get "logout", to: "sessions#destroy", as: "logout" end
Add the following code to the
application_controller.rb to handle the
current_user as well as the protective call to trigger the check for a
current_user through the application. By using the
before_action, we can be assured that the entire application is now covered by authentication.
class ApplicationController < ActionController::Base helper_method :current_user before_action :authenticate_user! def authenticate_user! unless current_user redirect_to login_path, notice: 'Please login' end end def current_user if session[:user_id] @current_user ||= User.find(session[:user_id]) else @current_user = nil end end end
For some controllers and specific views, we need to exclude them from
authenticate_user!, which we can do in the individual controllers, which will be necessary in our
class SessionsController < ApplicationController skip_before_action :authenticate_user! ... end
We can also exclude specific actions, which we need to do in our Users controller:
class UsersController < ApplicationController skip_before_action :authenticate_user!, only: [:new, :create] ... end
This way, no authentication is done for user registration, but you need to be a user to view the
The last thing we'll do is customize our Home index page to reflect that we've logged in and provide a link to logout:
<h1>Home#index</h1> <p>Find me in app/views/home/index.html.erb</p> <% if current_user %> Logged in as <%= current_user.email %>. <%= link_to "Log Out", logout_path %> <% else %> <%= link_to "Log In", login_path %> or <%= link_to "Sign Up", signup_path %> <% end %> <p><%= notice %></p>
To test it out, start the Rails server:
$ rails server
Then, in the browser, go to the root path
localhost:3000 and it should redirect to
localhost:3000/login. You will need to register, so click
sign up. Once registered, you should be able login.
Carson R Cole