Authentication with Devise in Rails

Flexible Authentication solution for Rails application
Luan Nguyen
Mar 23, 2020 · 4 min read

Source: @bradfordnicolas

Introduction

Devise is a flexible authentication solution for Rails, it supports to common functionality such as: create new account, sign in, sign up, reset password ...

It’s composed of 10 modules:
  • Database Authenticatable: hashes and stores a password in the database to validate the authenticity of a user while signing in. The authentication can be done both through POST requests or HTTP Basic Authentication.
  • Omniauthable: adds OmniAuth support.
  • Confirmable: sends emails with confirmation instructions and verifies whether an account is already confirmed during sign in.
  • Recoverable: resets the user password and sends reset instructions.
  • Registerable: handles signing up users through a registration process, also allowing them to edit and destroy their account.
  • Rememberable: manages generating and clearing a token for remembering the user from a saved cookie.
  • Trackable: tracks sign in count, timestamps and IP address.
  • Timeoutable: expires sessions that have not been active in a specified period of time.
  • Validatable: provides validations of email and password. It’s optional and can be customized, so you’re able to define your own validations.
  • Lockable: locks an account after a specified number of failed sign-in attempts. Can unlock via email or after a specified time period.

Installing 

Add devise gem
Add this to your Gemfile:
gem 'devise', '~> 4.7'

then run bundle to install the gem:
bundle install

Next, you need to run the generator:
rails generate devise:install

It’ll auto-generate 2 files:
  • config/initializers/devise.rb
  • config/locales/devise.en.yml
Generate User model
rails generate devise User

Output:
invoke active_record
  create db/migrate/YYYYMMDDHHMMSS_devise_create_users.rb
  create app/models/user.rb
invoke test_unit
  create test/models/user_test.rb
  create test/fixtures/users.yml
insert app/models/user.rb
route devise_for :users

After running the command, it generates:
  • A migration to create User table: YYYYMMDDHHMMSS_devise_create_users.rb
  • User model: app/models/user.rb
  • Add routes for user and test files.
Open User model (app/models/user.rb), you can see default devise modules:
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end

For this demo, we use additional 2 modules:
  • confirmable – Users will have to confirm their e-mails after registration before being allowed to sign in.
  • trackable - tracks sign in count, timestamps and IP address.
Modify User model like this:
devise :database_authenticatable, :registerable,
       :recoverable, :rememberable, :validatable, :confirmable, :trackable

Besides, you have to edit the migration file (db/migrate/YYYYMMDDHHMMSS_devise_create_users.rb)
Uncomment the following lines like this:
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      [...]

      ## Trackable
      t.integer  :sign_in_count, default: 0, null: false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

      ## Confirmable
      t.string   :confirmation_token
      t.datetime :confirmed_at
      t.datetime :confirmation_sent_at
      t.string   :unconfirmed_email # Only if using reconfirmable

      [...]
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

Then run the migration to create User table
rails db:migrate

View details User table on rails console:
class User < ApplicationRecord {
                        :id => :integer,
                     :email => :string,
        :encrypted_password => :string,
      :reset_password_token => :string,
    :reset_password_sent_at => :datetime,
       :remember_created_at => :datetime,
             :sign_in_count => :integer,
        :current_sign_in_at => :datetime,
           :last_sign_in_at => :datetime,
        :current_sign_in_ip => :inet,
           :last_sign_in_ip => :inet,
        :confirmation_token => :string,
              :confirmed_at => :datetime,
      :confirmation_sent_at => :datetime,
         :unconfirmed_email => :string,
                :created_at => :datetime,
                :updated_at => :datetime
}
Tips: I use awesome_print gem to view that on rails console

Configure Devise

Initialize devise: config/initializers/devise.rb 
Devise.setup do |config|
  [ ...]
  # ==> Mailer Configuration
  # Configure the e-mail address which will be shown in Devise::Mailer,
  # note that it will be overwritten if you use your own mailer class
  # with default "from" parameter.
  config.mailer_sender = 'your-email@mail.com'
  
  [...] # More setting
end

Set up the default URL options for the Devise mailer
In development environment: (config/environments/development.rb)
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

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

You should restart your application after changing Devise’s configuration options (this includes stopping spring). Otherwise, you will run into strange errors, for example, users being unable to login and route helpers being undefined.

Usage

Controller filters and helpers
Devise supports some helpers to use inside your controllers and views.
To set up a controller with user authentication, just add this before_action:
before_action :authenticate_user!

To verify if a user is signed in, use the helper:
user_signed_in?

For the current signed-in user, use this helper:
current_user

Add Sign_up/ Log_in / Log_out links
You can add HTML code below to your view application:
<% if user_signed_in? %>
  <%= link_to "Log out", destroy_user_session_path, method: :delete %>
<% else %>
  <%= link_to "Sign up", new_user_registration_path %> |
  <%= link_to "Log in", new_user_session_path %>
<% end %>

  • destroy_user_session_path: Log out path, method :delete is default HTTP method used to sign out a User
  • new_user_registration_path: Create new user path
  • new_user_session_path: Log in path.
Sign Up: Create a new User account by click to Sign_up link, you'll see the view like that:
Sign up Form


Log in: Click to Log_in link, you’ll see the form like that:
Log in Form

Conclusion

In this article, I help you step by step use Devise modules in Rails application to build authentication functions.

Full Code on Github:
https://github.com/thanhluanuit/devise-in-rails
References: