OmniAuth (Ruby)

Authentication LibraryOmniAuthRubyRailsOAuthOpenIDMulti-authRack

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