Skip
Arish's avatar

47. Devise Authentication


Devise

Devise is the most popular authentication gem for Rails. It provides a complete authentication solution with minimal setup.

Installation

ruby
1# Gemfile
2gem 'devise'
bash
1bundle install
2rails generate devise:install

Follow the setup instructions shown after installation.

Basic Setup

Generate User Model

bash
1rails generate devise User
2rails db:migrate

Configure Routes

ruby
1# config/routes.rb
2Rails.application.routes.draw do
3  devise_for :users
4  
5  # Your routes
6  root 'home#index'
7end

This adds routes for:

  • /users/sign_in - Login
  • /users/sign_up - Registration
  • /users/sign_out - Logout
  • /users/password/new - Forgot password
  • /users/password/edit - Reset password
  • /users/confirmation/new - Resend confirmation
  • /users/edit - Edit profile

Devise Modules

ruby
1# app/models/user.rb
2class User < ApplicationRecord
3  devise :database_authenticatable,  # Password authentication
4         :registerable,              # User registration
5         :recoverable,               # Password reset
6         :rememberable,              # Remember me
7         :validatable,               # Email/password validations
8         :confirmable,               # Email confirmation
9         :lockable,                  # Lock after failed attempts
10         :timeoutable,               # Session timeout
11         :trackable,                 # Track sign in count, timestamps, IP
12         :omniauthable               # OAuth authentication
13end

Controller Helpers

ruby
1# In any controller
2class ArticlesController < ApplicationController
3  before_action :authenticate_user!  # Require login
4  
5  def index
6    # current_user is available
7    @articles = current_user.articles
8  end
9end
10
11# In views
12<% if user_signed_in? %>
13  Welcome, <%= current_user.email %>
14  <%= link_to "Log out", destroy_user_session_path, method: :delete %>
15<% else %>
16  <%= link_to "Log in", new_user_session_path %>
17  <%= link_to "Sign up", new_user_registration_path %>
18<% end %>

Customizing Views

Generate views to customize them:

bash
1rails generate devise:views

This creates views in app/views/devise/:

  • sessions/new.html.erb - Login form
  • registrations/new.html.erb - Sign up form
  • registrations/edit.html.erb - Edit profile
  • passwords/new.html.erb - Forgot password
  • passwords/edit.html.erb - Reset password
  • confirmations/new.html.erb - Resend confirmation
  • mailer/ - Email templates

Custom Login Form

erb
1<!-- app/views/devise/sessions/new.html.erb -->
2<h2>Log In</h2>
3
4<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
5  <div class="field">
6    <%= f.label :email %>
7    <%= f.email_field :email, autofocus: true, autocomplete: "email", class: "form-control" %>
8  </div>
9
10  <div class="field">
11    <%= f.label :password %>
12    <%= f.password_field :password, autocomplete: "current-password", class: "form-control" %>
13  </div>
14
15  <% if devise_mapping.rememberable? %>
16    <div class="field">
17      <%= f.check_box :remember_me %>
18      <%= f.label :remember_me %>
19    </div>
20  <% end %>
21
22  <div class="actions">
23    <%= f.submit "Log in", class: "btn btn-primary" %>
24  </div>
25<% end %>
26
27<%= render "devise/shared/links" %>

Customizing Controllers

Generate controller to customize:

bash
1rails generate devise:controllers users
ruby
1# app/controllers/users/registrations_controller.rb
2class Users::RegistrationsController < Devise::RegistrationsController
3  before_action :configure_permitted_parameters
4  
5  protected
6  
7  def configure_permitted_parameters
8    devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :username])
9    devise_parameter_sanitizer.permit(:account_update, keys: [:name, :username, :avatar])
10  end
11  
12  def after_sign_up_path_for(resource)
13    dashboard_path
14  end
15end

Update routes:

ruby
1devise_for :users, controllers: {
2  registrations: 'users/registrations',
3  sessions: 'users/sessions'
4}

Adding Custom Fields

bash
1rails generate migration AddNameToUsers name:string
2rails db:migrate
ruby
1# app/controllers/application_controller.rb
2class ApplicationController < ActionController::Base
3  before_action :configure_permitted_parameters, if: :devise_controller?
4  
5  protected
6  
7  def configure_permitted_parameters
8    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
9    devise_parameter_sanitizer.permit(:account_update, keys: [:name, :avatar])
10  end
11end

Confirmable (Email Verification)

bash
1rails generate migration AddConfirmableToUsers confirmation_token:string:uniq confirmed_at:datetime confirmation_sent_at:datetime unconfirmed_email:string
2rails db:migrate
ruby
1# app/models/user.rb
2devise :confirmable
3
4# For existing users
5User.update_all(confirmed_at: Time.current)

Lockable (Account Locking)

bash
1rails generate migration AddLockableToUsers failed_attempts:integer unlock_token:string:uniq locked_at:datetime
2rails db:migrate
ruby
1devise :lockable
2
3# config/initializers/devise.rb
4config.lock_strategy = :failed_attempts
5config.unlock_strategy = :both
6config.maximum_attempts = 5
7config.unlock_in = 1.hour

OAuth with OmniAuth

ruby
1# Gemfile
2gem 'omniauth-google-oauth2'
3gem 'omniauth-github'
4gem 'omniauth-rails_csrf_protection'
ruby
1# config/initializers/devise.rb
2config.omniauth :google_oauth2, 
3  ENV['GOOGLE_CLIENT_ID'], 
4  ENV['GOOGLE_CLIENT_SECRET'],
5  scope: 'email,profile'
6
7config.omniauth :github, 
8  ENV['GITHUB_KEY'], 
9  ENV['GITHUB_SECRET'],
10  scope: 'user:email'
ruby
1# app/models/user.rb
2devise :omniauthable, omniauth_providers: [:google_oauth2, :github]
3
4def self.from_omniauth(auth)
5  where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
6    user.email = auth.info.email
7    user.password = Devise.friendly_token[0, 20]
8    user.name = auth.info.name
9  end
10end
ruby
1# app/controllers/users/omniauth_callbacks_controller.rb
2class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
3  def google_oauth2
4    handle_auth("Google")
5  end
6  
7  def github
8    handle_auth("Github")
9  end
10  
11  private
12  
13  def handle_auth(provider)
14    @user = User.from_omniauth(request.env["omniauth.auth"])
15    
16    if @user.persisted?
17      sign_in_and_redirect @user, event: :authentication
18      set_flash_message(:notice, :success, kind: provider) if is_navigational_format?
19    else
20      session["devise.auth_data"] = request.env["omniauth.auth"].except(:extra)
21      redirect_to new_user_registration_url
22    end
23  end
24end

Configuration

ruby
1# config/initializers/devise.rb
2Devise.setup do |config|
3  config.mailer_sender = 'noreply@example.com'
4  
5  # Password requirements
6  config.password_length = 8..128
7  
8  # Session timeout
9  config.timeout_in = 30.minutes
10  
11  # Remember me duration
12  config.remember_for = 2.weeks
13  
14  # Reset password token validity
15  config.reset_password_within = 6.hours
16  
17  # Sign out method
18  config.sign_out_via = :delete
19end

Devise provides a robust, battle-tested authentication solution for Rails!