OmniAuth (Ruby)
Authentication Library
OmniAuth (Ruby)
Overview
OmniAuth is a flexible authentication system for Ruby applications. Utilizing Rack middleware, it enables easy integration with OAuth, OpenID, and numerous other authentication providers. Using the strategy pattern, it allows plugin-style addition of various authentication services (Google, Facebook, Twitter, GitHub, etc.) and greatly simplifies authentication implementation in Rack-compatible frameworks including Rails.
Details
OmniAuth has been trusted as the de facto standard authentication library in Ruby web application development for many years. Key features include extensibility through strategy-based architecture, simultaneous support for multiple authentication providers, unified API interface, and CSRF protection functionality.
Since OmniAuth 2.0, CSRF protection is enabled by default for security enhancement, and the combination with the omniauth-rails_csrf_protection gem is recommended in Rails environments. Additionally, flexible configuration is possible according to use cases, from the :developer strategy for development environments to external provider strategies for full production.
The authentication flow is standardized, with all strategies using the common /auth/:provider/callback endpoint, and authentication information consistently provided as request.env['omniauth.auth'].
Pros and Cons
Pros
- Strategy Pattern: Plugin-style support for numerous authentication providers
- Unified API: Consistent interface regardless of provider differences
- Rack Integration: Easy use in major Ruby frameworks like Rails and Sinatra
- Rich Ecosystem: Over 100 authentication strategies available
- CSRF Protection: Compliance with security best practices
- Configuration Flexibility: Adaptable from development to production environments
Cons
- Ruby Only: Cannot be used with languages other than Ruby
- Setup Complexity: Initial configuration and strategy selection can be complex
- CSRF Configuration: CSRF protection configuration required in Rails environments
- Session Management: Application-side session management implementation required
Reference Pages
Code Examples
Gemfile Configuration
# Gemfile
gem 'omniauth'
gem 'omniauth-rails_csrf_protection' # CSRF protection for Rails
# Add required strategies
gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
gem 'omniauth-github'
Rails Initializer Configuration
# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
# Simple strategy for development environment
provider :developer unless Rails.env.production?
# Google OAuth2
provider :google_oauth2,
ENV['GOOGLE_CLIENT_ID'],
ENV['GOOGLE_CLIENT_SECRET']
# Facebook
provider :facebook,
ENV['FACEBOOK_APP_ID'],
ENV['FACEBOOK_APP_SECRET']
# GitHub
provider :github,
ENV['GITHUB_CLIENT_ID'],
ENV['GITHUB_CLIENT_SECRET']
end
# CSRF protection configuration (when using rack_csrf gem)
OmniAuth::AuthenticityTokenProtection.default_options(
key: "csrf.token",
authenticity_param: "_csrf"
)
Route Configuration
# config/routes.rb
Rails.application.routes.draw do
# OmniAuth callback
get '/auth/:provider/callback', to: 'sessions#create'
# Login page
get '/login', to: 'sessions#new'
# Logout
delete '/logout', to: 'sessions#destroy'
root 'home#index'
end
Sessions Controller
# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
# Display login page
end
def create
# Callback processing after OmniAuth authentication
auth_data = request.env['omniauth.auth']
user = User.find_or_create_by(email: auth_data.info.email) do |u|
u.name = auth_data.info.name
u.provider = auth_data.provider
u.uid = auth_data.uid
u.avatar_url = auth_data.info.image
end
if user.persisted?
session[:user_id] = user.id
redirect_to root_path, notice: 'Successfully logged in'
else
redirect_to login_path, alert: 'Login failed'
end
end
def destroy
session[:user_id] = nil
redirect_to root_path, notice: 'Successfully logged out'
end
end
User Model
# app/models/user.rb
class User < ApplicationRecord
validates :email, presence: true, uniqueness: true
validates :name, presence: true
validates :provider, presence: true
validates :uid, presence: true
def self.from_omniauth(auth_data)
where(provider: auth_data.provider, uid: auth_data.uid).first_or_create do |user|
user.email = auth_data.info.email
user.name = auth_data.info.name
user.avatar_url = auth_data.info.image
end
end
def display_name
name.presence || email.split('@').first
end
end
# Migration example
# rails generate migration CreateUsers email:string name:string provider:string uid:string avatar_url:string
Login View
<!-- app/views/sessions/new.html.erb -->
<div class="login-page">
<h1>Login</h1>
<div class="login-options">
<!-- For development environment -->
<% unless Rails.env.production? %>
<%= form_tag('/auth/developer', method: 'post', data: {turbo: false}) do %>
<button type='submit' class="btn btn-developer">
Developer Login
</button>
<% end %>
<% end %>
<!-- Google -->
<%= link_to '/auth/google_oauth2', method: :post,
data: {turbo: false},
class: 'btn btn-google' do %>
<i class="fab fa-google"></i> Sign in with Google
<% end %>
<!-- Facebook -->
<%= link_to '/auth/facebook', method: :post,
data: {turbo: false},
class: 'btn btn-facebook' do %>
<i class="fab fa-facebook"></i> Sign in with Facebook
<% end %>
<!-- GitHub -->
<%= link_to '/auth/github', method: :post,
data: {turbo: false},
class: 'btn btn-github' do %>
<i class="fab fa-github"></i> Sign in with GitHub
<% end %>
</div>
</div>
ApplicationController Authentication Helpers
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :authenticate_user!, except: [:index, :show]
private
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
def user_signed_in?
current_user.present?
end
helper_method :user_signed_in?
def authenticate_user!
unless user_signed_in?
redirect_to login_path, alert: 'Login required'
end
end
def require_admin
unless current_user&.admin?
redirect_to root_path, alert: 'Admin privileges required'
end
end
end
Multi-Provider Support
# When users can use multiple authentication providers
class UserAuth < ApplicationRecord
belongs_to :user
validates :provider, presence: true
validates :uid, presence: true
validates :user_id, uniqueness: { scope: [:provider, :uid] }
end
class User < ApplicationRecord
has_many :user_auths, dependent: :destroy
def self.from_omniauth(auth_data)
# Check for existing authentication
auth = UserAuth.find_by(provider: auth_data.provider, uid: auth_data.uid)
if auth
auth.user
else
# Search for existing user by email address
user = User.find_by(email: auth_data.info.email)
if user
# Add new authentication to existing user
user.user_auths.create!(
provider: auth_data.provider,
uid: auth_data.uid
)
else
# Create new user
user = User.create!(
email: auth_data.info.email,
name: auth_data.info.name,
avatar_url: auth_data.info.image
)
user.user_auths.create!(
provider: auth_data.provider,
uid: auth_data.uid
)
end
user
end
end
end
Creating Custom Strategy
# lib/omniauth/strategies/custom_provider.rb
module OmniAuth
module Strategies
class CustomProvider < OmniAuth::Strategies::OAuth2
option :name, 'custom_provider'
option :client_options, {
site: 'https://api.customprovider.com',
authorize_url: '/oauth/authorize',
token_url: '/oauth/token'
}
uid { raw_info['id'] }
info do
{
name: raw_info['name'],
email: raw_info['email'],
image: raw_info['avatar_url']
}
end
extra do
{
raw_info: raw_info
}
end
private
def raw_info
@raw_info ||= access_token.get('/me').parsed
end
end
end
end
# Use in config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :custom_provider, ENV['CUSTOM_CLIENT_ID'], ENV['CUSTOM_CLIENT_SECRET']
end
Error Handling
# config/initializers/omniauth.rb
OmniAuth.config.on_failure = Proc.new do |env|
# Custom processing on failure
SessionsController.action(:failure).call(env)
end
# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def failure
error = params[:message] || 'Authentication failed'
redirect_to login_path, alert: "Login error: #{error}"
end
end
# config/routes.rb
get '/auth/failure', to: 'sessions#failure'
Sinatra Usage Example
# app.rb
require 'sinatra'
require 'omniauth'
require 'omniauth-google-oauth2'
class MyApp < Sinatra::Base
use Rack::Session::Cookie, secret: 'your-secret-key'
use OmniAuth::Builder do
provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET']
end
get '/' do
if session[:user]
"Hello, #{session[:user]['name']}!"
else
'<a href="/auth/google_oauth2">Sign in with Google</a>'
end
end
get '/auth/:provider/callback' do
auth = request.env['omniauth.auth']
session[:user] = {
'name' => auth.info.name,
'email' => auth.info.email
}
redirect '/'
end
get '/logout' do
session[:user] = nil
redirect '/'
end
end