Warden

authentication libraryRubyRailsRackmiddlewaresecuritystrategy pattern

Authentication Library

Warden

Overview

Warden is a general Rack authentication framework for Ruby applications. It operates as Rack middleware and provides a modular and flexible authentication system using the strategy pattern. It serves as the foundation for Rails authentication libraries like Devise and enables low-level authentication control.

Details

Warden was created by Daniel Neighman in 2009 as a Rack authentication framework and continues to be actively developed. Warden::Manager serves as the entry point, integrating directly into the Rack application stack. For each request, a Warden::Proxy object is injected into the Rack environment to handle authentication processing.

It adopts the Strategy pattern, allowing developers to define various authentication mechanisms as independent strategies. All strategies must inherit from Warden::Strategies::Base and implement the authenticate! method. Strategies can return multiple outcomes such as success!, fail!, fail, redirect!, custom!, and pass, enabling flexible authentication flows.

Session management functionality is built-in, using Warden::SessionSerializer to handle serialization/deserialization of user objects. Additionally, a powerful hook system including after_set_user, after_authentication, before_logout, and on_request allows custom code execution at various points in the authentication lifecycle.

Advantages and Disadvantages

Advantages

  • Rack-level integration: Authentication can be accessed beyond the application layer, including mountable engines and routing constraints
  • Framework agnostic: Being Rack-based, it can be used with Ruby frameworks other than Rails
  • Advanced modular design: Use only necessary features, avoiding bloat from unnecessary functionality
  • Strategy chaining: Easy combination of multiple authentication strategies like password, OmniAuth, and Basic authentication
  • Fine-grained control: Low-level API enables implementation of authentication flows that don't fit standard patterns
  • Rich hooks: Custom processing can be inserted at each stage of the authentication process

Disadvantages

  • High learning curve: Complex setup and concepts can be challenging for beginners
  • Low-level implementation: More manual configuration compared to Devise, not suitable for rapid development
  • Limited documentation: Fewer learning resources compared to higher-level libraries
  • Rails integration overhead: Additional work required for integration with Rails-specific features
  • Maintainability: Low-level implementation may increase code complexity

Reference Pages

Code Examples

Basic Setup in Rack Application

# config.ru or Rails initializer
use Warden::Manager do |config|
  config.default_strategies :password
  config.failure_app = lambda { |env|
    [401, {'Content-Type' => 'text/plain'}, ['Unauthorized']]
  }
end

# Rails configuration example
Rails.application.config.middleware.insert_after ActionDispatch::Session::CookieStore, Warden::Manager do |manager|
  manager.default_strategies :password
  manager.failure_app = lambda { |env|
    SessionsController.action(:new).call(env)
  }
end

Custom Authentication Strategy Implementation

# Password-based authentication strategy
Warden::Strategies.add(:password) do
  def valid?
    params[:username] && params[:password]
  end
  
  def authenticate!
    user = User.find_by_username(params[:username])
    
    if user && user.authenticate(params[:password])
      success!(user)
    else
      fail!("Invalid credentials")
    end
  end
end

# Token-based authentication strategy
Warden::Strategies.add(:api_token) do
  def valid?
    request.headers['Authorization'].present?
  end
  
  def authenticate!
    token = request.headers['Authorization'].gsub(/^Bearer /, '')
    user = User.find_by_api_token(token)
    
    if user
      success!(user)
    else
      fail!("Invalid token")
    end
  end
end

Using Authentication in Controllers

class ApplicationController < ActionController::Base
  # Access to Warden
  def warden
    env['warden']
  end
  
  # Require authentication
  def authenticate_user!
    warden.authenticate!
  end
  
  # Get current user
  def current_user
    warden.user
  end
  
  # Check if user is signed in
  def user_signed_in?
    warden.authenticated?
  end
end

class SessionsController < ApplicationController
  def create
    # Execute authentication (throws exception on failure)
    user = warden.authenticate!(:password)
    redirect_to dashboard_path
  end
  
  def destroy
    warden.logout
    redirect_to root_path
  end
end

Utilizing Hook System

# Register hooks in Warden configuration
Warden::Manager.after_set_user do |user, auth, opts|
  # Code executed after user is set
  Rails.logger.info "User #{user.id} logged in"
end

Warden::Manager.before_logout do |user, auth, opts|
  # Code executed before logout
  user.update(last_logout_at: Time.current)
end

Warden::Manager.after_authentication do |user, auth, opts|
  # Code executed after successful authentication
  user.increment!(:sign_in_count)
end

Test Environment Configuration

# RSpec configuration example
RSpec.configure do |config|
  config.include Warden::Test::Helpers
  
  config.before(:suite) do
    Warden.test_mode!
  end
  
  config.after(:each) do
    Warden.test_reset!
  end
end

# Login in tests
def sign_in(user)
  login_as(user, scope: :user)
end

# Logout in tests
def sign_out
  logout(:user)
end

API Authentication Usage

class ApiController < ApplicationController
  before_action :authenticate_api_user!
  
  private
  
  def authenticate_api_user!
    warden.authenticate!(:api_token)
  rescue Warden::NotAuthenticated
    render json: { error: 'Unauthorized' }, status: 401
  end
end