Devise
Authentication Library
Devise
Overview
Devise is a flexible authentication solution for Ruby on Rails based on Warden. As of 2025, it has established itself as the most widely used authentication library in the Ruby on Rails ecosystem, providing comprehensive functionality to rapidly build robust user authentication systems. It comprehensively supports all authentication features required for modern web applications, including database authentication, password recovery, email confirmation, account registration, session management, token-based authentication, and OAuth integration. Through its modular design, you can select only the necessary features, and through deep integration with Rails, it achieves both simplified configuration and powerful customization capabilities.
Details
Devise 4.x series provides a robust authentication system based on the Warden middleware foundation. By combining 10 main modules (Database Authenticatable, Confirmable, Recoverable, Registerable, Rememberable, Trackable, Timeoutable, Validatable, Lockable, Omniauthable), you can build authentication functionality tailored to your application requirements. Complete integration with ActiveRecord allows you to easily add authentication features to user models, working in conjunction with Rails form helpers, validations, I18n, and mailers. Custom views, controllers, and route configurations enable complete customization of user interfaces and authentication flows.
Key Features
- Modular Design: Select and use only the authentication features you need
- Rails Integration: Complete integration with ActiveRecord, ActionMailer, and routing
- Security: Password encryption with bcrypt, CSRF protection, secure session management
- OAuth Support: Social login functionality through OmniAuth integration
- Rich Features: Password reset, email confirmation, account locking, login tracking
- Customization: Complete customization support for views, controllers, and authentication flows
Advantages and Disadvantages
Advantages
- Deep integration with Rails ecosystem enables rapid and efficient authentication system construction
- Rich functionality can handle complex authentication requirements
- Modular design allows starting with minimal features and gradual expansion
- Comprehensive documentation, community support, and long-term track record provide reliability
- OAuth integration makes it easy to connect with major social platforms
- Security best practices are automatically applied
Disadvantages
- Rails-only library, cannot be used with other Ruby frameworks
- Rich functionality leads to high learning costs and complexity for beginners
- Customization requires deep knowledge of Devise and Rails
- Configuration complexity can be excessive for simple authentication needs
- Monolithic design creates constraints in microservice environments
- Many default settings can cause unintended behavior
Reference Pages
Usage Examples
Basic Installation and Setup
# Gemfile - Add Devise
gem 'devise'
# OmniAuth integration (optional)
gem 'omniauth'
gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'
gem 'omniauth-github'
gem 'omniauth-rails_csrf_protection'
# Install Devise
bundle install
# Generate Devise initializer file
rails generate devise:install
# Generate User model
rails generate devise User
# Run database migration
rails db:migrate
# Generate Devise views (for customization)
rails generate devise:views
# Generate Devise controllers (for advanced customization)
rails generate devise:controllers users
# config/initializers/devise.rb - Devise basic configuration
Devise.setup do |config|
# Mailer sender address
config.mailer_sender = '[email protected]'
# Session storage key
config.case_insensitive_keys = [:email]
config.strip_whitespace_keys = [:email]
# Password settings
config.password_length = 8..128
config.reset_password_within = 6.hours
# Account lock settings
config.lock_strategy = :failed_attempts
config.unlock_strategy = :both
config.maximum_attempts = 10
config.unlock_in = 1.hour
# Remember me settings
config.remember_for = 2.weeks
config.extend_remember_period = false
# Session timeout settings
config.timeout_in = 30.minutes
# Email confirmation settings
config.reconfirmable = true
config.confirm_within = 3.days
# Login attempt tracking
config.sign_in_after_reset_password = true
config.sign_in_after_confirmation = true
# Sign out settings
config.sign_out_via = :delete
end
User Model and Module Configuration
# app/models/user.rb - User model configuration
class User < ApplicationRecord
# Devise module configuration
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:confirmable, :lockable, :trackable, :timeoutable,
:omniauthable, omniauth_providers: [:google_oauth2, :facebook, :github]
# Custom validations
validates :first_name, presence: true, length: { maximum: 50 }
validates :last_name, presence: true, length: { maximum: 50 }
validates :phone_number, format: { with: /\A[\d\-\s\+\(\)]+\z/, allow_blank: true }
# Associations
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
has_one_attached :avatar
# Custom methods
def full_name
"#{first_name} #{last_name}"
end
def display_name
full_name.present? ? full_name : email
end
# OmniAuth integration method
def self.from_omniauth(auth)
where(email: auth.info.email).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0, 20]
user.first_name = auth.info.first_name || auth.info.name&.split&.first
user.last_name = auth.info.last_name || auth.info.name&.split&.last
user.provider = auth.provider
user.uid = auth.uid
# Skip email confirmation (already confirmed via OmniAuth)
user.skip_confirmation!
end
end
# Skip password requirement (for OmniAuth users)
def password_required?
(provider.blank? || !password.blank?) && super
end
end
OmniAuth Integration and Social Login
# app/controllers/users/omniauth_callbacks_controller.rb - OmniAuth callbacks
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
handle_auth("Google")
end
def facebook
handle_auth("Facebook")
end
def github
handle_auth("GitHub")
end
def failure
set_flash_message! :alert, :failure, kind: OmniAuth::Utils.camelize(failed_strategy.name), reason: failure_message
redirect_to new_user_session_path
end
private
def handle_auth(kind)
@user = User.from_omniauth(request.env["omniauth.auth"])
if @user.persisted?
flash[:notice] = I18n.t 'devise.omniauth_callbacks.success', kind: kind
sign_in_and_redirect @user, event: :authentication
else
session["devise.#{kind.downcase}_data"] = request.env["omniauth.auth"].except('extra')
flash[:alert] = @user.errors.full_messages.join("\n")
redirect_to new_user_registration_url
end
end
end
# config/initializers/omniauth.rb - OmniAuth configuration
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2,
Rails.application.credentials.google[:client_id],
Rails.application.credentials.google[:client_secret],
{
scope: 'email,profile',
prompt: 'select_account',
image_aspect_ratio: 'square',
image_size: 50,
access_type: 'offline'
}
provider :facebook,
Rails.application.credentials.facebook[:app_id],
Rails.application.credentials.facebook[:app_secret],
{
scope: 'email,public_profile',
info_fields: 'first_name,last_name,email,picture.width(200).height(200)'
}
provider :github,
Rails.application.credentials.github[:client_id],
Rails.application.credentials.github[:client_secret],
scope: 'user:email'
end
# CSRF protection
OmniAuth.config.request_validation_phase = OmniAuth::AuthenticityTokenProtection.new
Custom Controllers and Routing
# config/routes.rb - Routing configuration
Rails.application.routes.draw do
# Standard Devise routes
devise_for :users, controllers: {
sessions: 'users/sessions',
registrations: 'users/registrations',
passwords: 'users/passwords',
confirmations: 'users/confirmations',
unlocks: 'users/unlocks',
omniauth_callbacks: 'users/omniauth_callbacks'
}
# Custom route additions
devise_scope :user do
get 'login', to: 'users/sessions#new'
get 'logout', to: 'users/sessions#destroy'
get 'signup', to: 'users/registrations#new'
end
root 'home#index'
end
# app/controllers/users/registrations_controller.rb - Registration controller customization
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
# POST /resource
def create
super do |resource|
if resource.persisted?
UserMailer.welcome_email(resource).deliver_later
end
end
end
# PUT /resource
def update
super
end
protected
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [
:first_name, :last_name, :phone_number, :avatar
])
end
def configure_account_update_params
devise_parameter_sanitizer.permit(:account_update, keys: [
:first_name, :last_name, :phone_number, :avatar
])
end
def update_resource(resource, params)
# Allow account update without password
if params[:password].blank? && params[:password_confirmation].blank?
params.delete(:password)
params.delete(:password_confirmation)
params.delete(:current_password)
resource.update_without_password(params)
else
resource.update_with_password(params)
end
end
def after_sign_up_path_for(resource)
stored_location_for(resource) || dashboard_path
end
def after_update_path_for(resource)
profile_path
end
end
View Customization and Email
<!-- app/views/devise/sessions/new.html.erb - Login form -->
<div class="authentication-wrapper">
<div class="authentication-inner">
<div class="card">
<div class="card-body">
<%= form_for(resource, as: resource_name, url: session_path(resource_name),
local: true, html: { class: "authentication-form" }) do |f| %>
<div class="form-header">
<h4 class="card-title">Login</h4>
<p class="card-text">Sign in to your account</p>
</div>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="form-group">
<%= f.label :email, class: "form-label" %>
<%= f.email_field :email, autofocus: true, autocomplete: "email",
class: "form-control", placeholder: "Enter email address" %>
</div>
<div class="form-group">
<%= f.label :password, class: "form-label" %>
<%= f.password_field :password, autocomplete: "current-password",
class: "form-control", placeholder: "Enter password" %>
</div>
<% if devise_mapping.rememberable? %>
<div class="form-check">
<%= f.check_box :remember_me, class: "form-check-input" %>
<%= f.label :remember_me, "Keep me signed in", class: "form-check-label" %>
</div>
<% end %>
<div class="form-actions">
<%= f.submit "Login", class: "btn btn-primary btn-block" %>
</div>
<% end %>
<!-- Social login -->
<div class="divider">
<span>or</span>
</div>
<div class="social-auth">
<%= link_to "Login with Google", user_google_oauth2_omniauth_authorize_path,
method: :post, class: "btn btn-google btn-block" %>
<%= link_to "Login with Facebook", user_facebook_omniauth_authorize_path,
method: :post, class: "btn btn-facebook btn-block" %>
<%= link_to "Login with GitHub", user_github_omniauth_authorize_path,
method: :post, class: "btn btn-github btn-block" %>
</div>
<!-- Links -->
<div class="auth-links">
<%= render "devise/shared/links" %>
</div>
</div>
</div>
</div>
</div>
Security and Access Control
# app/controllers/application_controller.rb - Authentication control
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :authenticate_user!
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [
:first_name, :last_name, :phone_number
])
devise_parameter_sanitizer.permit(:account_update, keys: [
:first_name, :last_name, :phone_number
])
end
# Customize redirect destination after user authentication
def after_sign_in_path_for(resource)
stored_location_for(resource) || dashboard_path
end
def after_sign_out_path_for(resource_or_scope)
root_path
end
# Permission check
def ensure_admin
redirect_to root_path unless current_user&.admin?
end
end
Testing and RSpec Integration
# spec/support/devise.rb - Test configuration
RSpec.configure do |config|
config.include Devise::Test::ControllerHelpers, type: :controller
config.include Devise::Test::IntegrationHelpers, type: :request
config.include Warden::Test::Helpers
end
# spec/factories/users.rb - Factory configuration
FactoryBot.define do
factory :user do
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
email { Faker::Internet.unique.email }
password { 'password123' }
password_confirmation { 'password123' }
confirmed_at { Time.current }
trait :unconfirmed do
confirmed_at { nil }
end
trait :locked do
locked_at { Time.current }
failed_attempts { 10 }
end
trait :admin do
admin { true }
end
trait :with_oauth do
provider { 'google_oauth2' }
uid { Faker::Number.unique.number(digits: 10) }
end
end
end
# spec/models/user_spec.rb - User model tests
RSpec.describe User, type: :model do
describe 'validations' do
it { should validate_presence_of(:email) }
it { should validate_uniqueness_of(:email).case_insensitive }
it { should validate_presence_of(:first_name) }
it { should validate_presence_of(:last_name) }
end
describe 'devise modules' do
it { should have_db_column(:email) }
it { should have_db_column(:encrypted_password) }
it { should have_db_column(:reset_password_token) }
it { should have_db_column(:confirmation_token) }
end
describe '#full_name' do
let(:user) { build(:user, first_name: 'John', last_name: 'Doe') }
it 'returns the full name' do
expect(user.full_name).to eq('John Doe')
end
end
describe '.from_omniauth' do
let(:auth) do
OmniAuth::AuthHash.new({
provider: 'google_oauth2',
uid: '123456789',
info: {
email: '[email protected]',
first_name: 'John',
last_name: 'Doe'
}
})
end
it 'creates a new user from omniauth data' do
expect {
User.from_omniauth(auth)
}.to change(User, :count).by(1)
end
it 'finds existing user by email' do
existing_user = create(:user, email: auth.info.email)
user = User.from_omniauth(auth)
expect(user).to eq(existing_user)
end
end
end