Authentication Adding User Authentication with Devise

In this demonstration, I will show how to add user authentication with the Devise gem.

General Steps

In general, the steps for adding user authentication are as follows.

Adding Authentication to the Quiz Me App

To demonstrate the steps for adding authentication, we will be adding it to the Quiz Me App.

Base App Code

Step 1 Install and Set Up Devise Gem

Substep Install Devise Gem. To install Devise, we add the following to the bottom of the Gemfile.

# Authentication
gem 'devise'

Afterwards, run bundle install in the terminal.

Substep Run Devise Installation Command. To finish the installation, we need to run the following command.

rails generate devise:install

If devise has been installed correctly you should see the following message in the terminal:

Some setup you must do manually if you haven\'t yet:

  1. Ensure you have defined default url options in your environments files. Here
    is an example of default_url_options appropriate for a development environment
    in config/environments/development.rb:

      config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

    In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
    For example:

      root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
    For example:

      <p class="notice"><%= notice %></p>
      <p class="alert"><%= alert %></p>

  4. You can copy Devise views (for customization) to your app by running:

      rails g devise:views

Step 2 Generate User Model

Substep Generate Devise User Model. To create the User model, we run the following command.

rails generate devise User

The command will generate a new migration (something like db/migrate/20191113155313_devise_create_users.rb), a new model class (app/models/user.rb), and it will add the following declaration to routes.rb which creates all the standard Devise routes for the User class:

devise_for :users

Substep Add Seeds. To add seeds for the User model, we create them like any other seed object. By default Devise Users only need an email and a password.

user1 = User.create!(
  email: "bob@email.com",
  password: "password"
)

user2 = User.create!(
  email: "alice@email.com",
  password: "password"
)

# Quiz and Question Seeds...

To include the necessary links, we will be editing the navbar of the Quiz Me app.

When visiting a site that includes authentication, we expect to be offered the chance to sign up or sign in. If we are already signed in, we want to be offered the ability to sign out or view our profile. Devise already takes care of the views and forms, but we need to handle the logic of when and where these links are displayed. We can do this with the help of the user_signed_in? Devise helper which checks to see if a user is signed in, and the current_user helper which retrieves the currently signed-in User.

The relevant route helpers that Devise provides for us are:

In the file views/shared/_navbar.erb.html, add a second <ul> block that includes the following code:

<nav class="navbar navbar-expand-lg navbar-light bg-light px-5">
  <a class="navbar-brand" href="#">QuizMe</a>
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>

  <div class="collapse navbar-collapse" id="navbarSupportedContent">
    <ul class="navbar-nav mr-auto">
      <li class="nav-item <%= active_class(quizzes_path) %>">
        <%= link_to 'Home', quizzes_path, class: 'nav-link' %>
      </li>
    </ul>
    <ul class="navbar-nav">
      <% if user_signed_in? %>
        <li class="nav-item <%= active_class(edit_user_registration_path) %>">
          <%= link_to "Hi, #{current_user.email}", edit_user_registration_path, class: 'nav-link' %>
        </li>
        <li class="nav-item">
          <%= link_to 'Sign Out', destroy_user_session_path, method: :delete, class: 'nav-link' %>
        </li>
      <% else %>
        <li class="nav-item <%= active_class(new_user_session_path) %>">
          <%= link_to 'Sign In', new_user_session_path, class: 'nav-link' %>
        </li>
        <li class="nav-item <%= active_class(new_user_registration_path) %>">
          <%= link_to 'Sign Up', new_user_registration_path, class: 'nav-link' %>
        </li>
      <% end %>
    </ul>
  </div>
</nav>

Test It!

To confirm that we made this change correctly, start the server, navigate to http://localhost:3000 and see that there are sign-in and sign-up links on the right side of the navbar.

A screenshot of the sign-in and sign-up links in the navbar of the Quiz-Me App.

Figure 1. The sign-in and sign-up links in the navbar of the Quiz-Me App.

After registering, the links should change to “Hi, {current user}” and “Sign Out”

A screenshot of the profile and sign-out links in the navbar of the Quiz-Me App after signing in.

Figure 2. The profile and sign-out links in the navbar of the Quiz-Me App after signing in..

Step 3 Changeset

Step 4 Require Sign-In for Controller Actions

To keep signed-out users from accessing any Quiz or Question actions that we don’t want them to see, we can add a before_action :authenticate_user! statement to our controllers. This will redirect any unauthenticated users to the sign-in page before executing any actions in the controllers. For the Quizzes controller, we exempt the index action from requiring authentication because we are using the quiz index page as our home page.

class QuizzesController < ApplicationController
  before_action :authenticate_user!, except: [:index]

  # Controller Actions...
end
class QuestionsController < ApplicationController
  before_action :authenticate_user!

  # Controller Actions...
end

For more information on before_action statements, see the explanation of filters in the Rails Guides. :authenticate_user! is a method provided by Devise.

Test It!

To confirm that we made this change correctly, we sign out and navigate to http://localhost:3000/quizzes/1 and see that you are redirected to the sign-in page. If you then enter login information, you will be taken to http://localhost:3000/quizzes/1 as originally requested.

Step 4 Changeset

Conclusion

Following the above steps, we have now an app that requires authentication and keeps unauthenticated users from accessing its primary functions.

Demo App Code