Note: This document is a work in progress. You can help improve it.

Simple Authentication with Bcrypt

Steps

  1. Create a user model with name, email and password_digest with: rails generate model user name email password_digest

  2. Run rails db:migrate

  3. Add these routes below

    # Path: config/routes.rb
    
      get '/signup' => 'users#new'
      post '/users' => 'users#create'
    
  4. Create a users controller with a new and create action:

    # Path: app/controllers/users_controller.rb
    
    class UsersController < ApplicationController
    
      def new
        @user = User.new
      end
    
      def create
        user = User.new(user_params)
        if user.save
          session[:user_id] = user.id
          redirect_to root_path
        else
          redirect_to signup_path
        end
      end
    
    private
    
      def user_params
        params.require(:user).permit(:name, :email, :password, :password_confirmation)
      end
    end
    
  5. Now create the view file where we put the signup form.

    <!-- Path: app/views/users/new.html.erb -->
    
    <div class="page-header">
      <h1>Signup!</h1>
    </div>
    
    <%= form_for(@user, html: { class: "form-horizontal", role: "form" }) do |f| %>
      <div class="form-group">
        <%= f.label :name, class: "col-sm-2 control-label" %>
        <div class="col-sm-10">
          <%= f.text_field :name, class: "form-control" %>
        </div>
      </div>
      <div class="form-group">
        <%= f.label :email, class: "col-sm-2 control-label" %>
        <div class="col-sm-10">
          <%= f.text_field :email, class: "form-control" %>
        </div>
      </div>
      <div class="form-group">
        <%= f.label :password, class: "col-sm-2 control-label" %>
        <div class="col-sm-10">
          <%= f.password_field :password, class: "form-control" %>
        </div>
      </div>
      <div class="form-group">
        <%= f.label :password_confirmation, class: "col-sm-2 control-label" %>
        <div class="col-sm-10">
          <%= f.password_field :password_confirmation, class: "form-control" %>
        </div>
      </div>
      <%= f.submit "Submit", class: "btn btn-primary" %>
    <% end %>
    
  6. Uncomment 'bcrypt' in the Gemfile

  7. Add has_secure_password to add encryption of the user's password

    # Path: app/models/user.rb
    
    class User < ActiveRecord::Base
      has_secure_password
    end
    
  8. Run bundle install

  9. Create a sessions controller to create (login) and destroy (logout) sessions.

    # Path: app/controllers/sessions_controller.rb
    
    class SessionsController < ApplicationController
    
      # logging in
      def new
      end
    
      # handle the post from the login page
      def create
        # Extract the email and password from the params
        email = params[:email]
        password = params[:password]
    
        # Find the user by their email
        user = User.find_by(email: email)
    
        # If we found a user and their password checks out
        if user && user.authenticate(password)
          # Save the user_id in the session cookie
          session[:user_id] = user.id
    
          # logged in!
          redirect_to root_path
        else
          # Nope, something went wrong
          redirect_to login_path
        end
      end
    
      # logout
      def destroy
        # Remove their user_id from the session
        session[:user_id] = nil
        redirect_to root_path
      end
    end
    
  10. Create a form for user's to login with.

    <!-- path: app/views/sessions/new.html.erb -->
    
    <div class="page-header">
      <h1>Login</h1>
    </div>
    
    <%= form_tag(login_path, html: { class: "form-horizontal", role: "form" }) do %>
      <div class="form-group">
        <%= label_tag :email, class: "col-sm-2 control-label" %>
        <div class="col-sm-10">
          <%= text_field_tag :email, class: "form-control" %>
        </div>
      </div>
      <div class="form-group">
        <%= label_tag :password, class: "col-sm-2 control-label" %>
        <div class="col-sm-10">
          <%= password_field_tag :password, class: "form-control" %>
        </div>
      </div>
      <%= submit_tag "Submit", class: "btn btn-primary" %>
    <% end %>
    
  11. Update your routes file to include new routes for the session controller.

    # Path: config/routes.rb
    
      get '/login'  => 'sessions#new'
      post '/login' => 'sessions#create'
      get '/logout' => 'sessions#destroy'
    
  12. We will add a few methods to the ApplicationController to allow us to find the current user.

    # Path: app/controllers/application_controller.rb
    
    class ApplicationController < ActionController::Base
    
      # Returns the current use if logged in
      def current_user
        @current_user ||= User.find(session[:user_id]) if session[:user_id]
      end
      helper_method :current_user
    
      # Returns a boolean representing if the user is logged in
      def logged_in?
        !!current_user
      end
      helper_method :logged_in?
    
      # Method to use in a filter to ensure the user is logged in
      def authorize!
        redirect_to login_path unless logged_in?
      end
    end
    
  13. Now we can add a before_filter to ensure we authorize the user. We could add this to the ApplicationController but then we would have to exclude this from controllers that do not require login (e.g. user controller, session controller, homepage controller, etc.)

    # path: app/controller/widgets_controller.rb
    
    # This is just an example controller, you would add this to your *own* controller files
    class WidgetsController < ApplicationController
      before_filter :authorize!
    end
    
  14. We can use the current_user and logged_in? methods to customize pages, even the appliction layout file

    <!-- Path: app/views/layouts/application_layout.html.erb -->
    <% if logged_in? %>
      Logged in: <%= current_user.email %> | <%= link_to "Logout", logout_path %>
    <% else %>
      <%= link_to 'Login', login_path %> | <%= link_to 'Signup', signup_path %>
    <% end %>