Authentication with Warden, devise-less

For most of us, using warden means using devise. Devise served me well but it's big and there's a lot of convention that you need to abide by. Getting up & running with devise is fairly simple. Warden needs a little bit more to get started and I'm going to show you how to implement it to have a similar setup.

Warden is great. But it might not be as simple to get started with it than it is with devise. On the other hand, the whole authentication process is done by you which means you have a lot more control and flexibility when your need changes.

If you are moving from devise to warden, the nice thing is you can keep your database schema intact.

What devise use

Warden uses strategies to try to authenticate sessions. However, it provides no strategy, you have to implement them yourself. Devise implements a few that you may already know.

What devise adds to warden

Devise adds a lot of features and stuff, and before you take the plunge it's probably a good idea to know what is going to be missing by using warden alone.

Warden, sounds cool. How do I implement it?

Now that all the disclaimers have been raised, let's get down to business and see what can be done with warden alone. First of all, you will need to add warden to your project's Gemfile.


# Gemfile
gem 'warden'

Migration

In this example, I'll use a model name User. Our model will have the following features:

Here's the migration needed for these to works


class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :email, :null => false
      t.string :encrypted_password, :null => false, :default => ""

      # Confirmable
      t.string   :confirmation_token
      t.datetime :confirmed_at
      t.datetime :confirmation_sent_at
   end

    add_index :users, :email, :unique => true
    add_index :users, :confirmation_token,   :unique => true
end

Routes

Three controllers needs to take care of log in, registration and confirmation. This can be set the way you want. For clarity's sake, my routes are going to be very simple.


YourProject::Application.routes.draw do
  resources :users do
    collection do
      resource :registrations, only: [:show, :create]
      resource :sessions, only: [:new, :create, :destroy]
      resource :confirmations, only: [:show]
    end
  end
end

Add warden as a rack middleware

Rack is a specification that lets you connect many application to your server. Maybe you didn't know but rails has a lot of rack instances. Each of them handles a part of the request/response process and the order matters. In your project's root folder list all the middleware like so


$ rake middleware

use ActionDispatch::Static
use Rack::Lock
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use ActionDispatch::Head
use Rack::ConditionalGet
use Rack::ETag
use ActionDispatch::BestStandardsSupport
run YouProject::Application.routes

Since the order is important where would warden go? Wiki says that you should add warden after a session middleware. Don't. If you add it just after the session store, the flash won't be available to you in some places and you won't be able to access them.

You want to add warden after ActionDispatch::Flash.

In rails application.rb there's a convenience method to add warden safely.

# application.rb
Warden::Manager.serialize_into_session do |user|
  user.id
end

Warden::Manager.serialize_from_session do |id|
  User.find_by_id(id)
end

config.middleware.insert_after ActionDispatch::Flash, Warden::Manager do |manager|
end

I also added a way for Warden to serialize from and to the session cookie. This way, Warden knows how to get the User from the database.

Let's make sure warden is now a member of our middleware stack


$ rake middleware

use ActionDispatch::Static
use Rack::Lock
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use Warden::Manager
use ActionDispatch::ParamsParser
use ActionDispatch::Head
use Rack::ConditionalGet
use Rack::ETag
use ActionDispatch::BestStandardsSupport
run YouProject::Application.routes

Configure warden to use a failure application.

Now, warden needs a failure application. what does this means? How complicated is this going to be? It's not obvious. When authentication fails, warden calls this application and you  handle the request from there.

Ideally, you want to redirect to the log in page and show a flash message. Browsing devise's source, I learned that ActionController::Metal is Rack compliant. That means that my rack application can be a controller! Well now it's become very much easier to deal with and well, let's use devise's concept and apply it to our configuration.


class UnauthorizedController < ActionController::Metal
  include ActionController::UrlFor
  include ActionController::Redirecting
  include Rails.application.routes.url_helpers
  include Rails.application.routes.mounted_helpers

  delegate :flash, :to => :request

  def self.call(env)
    @respond ||= action(:respond)
    @respond.call(env)
  end

  def respond
    unless request.get?
      message = env['warden.options'].fetch(:message, "unauthorized.user")
      flash.alert = I18n.t(message)
    end

    redirect_to new_sessions_url
  end
end

First, UnauthorizedController is a subclass of ActionController::Metal, not ApplicationController. Failure application is called outside of your expected rails environment. A few modules are included to provide methods that you would normally expect in a controller.

def self.call(env) is the method that makes this controller Rack compliant. The action(:respond) method is then called and returned. What this does is that it initialize the UnauthorizedController and call the method UnauthorizedController#respond which is pretty much like a normal action in your normal controller.

I added a small condition to check wether the request is a GET method or something else. This was just because I wanted to show a flash message only if it's not a GET request. Your mileage may vary.

The use of the flash here is the reason why warden had to be inserted after flash or it wouldn't have worked: Flash would be initialized with request's hash after the call to this failure application.

At the end, a simple redirection to the login page.

Warden doesn't know yet about this failure application. Inform it in application.rb

config.middleware.insert_after ActionDispatch::Flash, Warden::Manager do |manager|
  manager.failure_app = UnauthorizedController
end

User's password & strategy

So far, we've set some stuff that is indirect to a user logging in. It's time for our User model to receive some love. Handling password can be scary. Bcrypt is very easy to use is very light weight. Add it to your gemfile


gem 'bcrypt-ruby'

Now, open up your User model and include Bcrypt to your model.


require 'bcrypt'

class User < ActiveRecord::Base
  include BCrypt
  attr_accessible :email
  validates :email, :presence => true

  before_create :generate_confirmation_token

  def password
    @password ||= Password.new(self.encrypted_password)
  end

  def password=(new_password)
    @password = Password.create(new_password)
    self.encrypted_password = @password
  end

  def confirm!
    self.confirmation_token = nil
    self.confirmed_at = Time.now.utc
    self.save!
  end

private

  def generate_confirmation_token
    loop do
      token = SecureRandom.urlsafe_base64
      unless User.where(:confirmation_token => token).any?
        self.confirmation_token = token
        self.confirmation_sent_at = Time.now.utc
        break
      end
    end
  end
end

I added the confirmation algorithm here as well because it's pretty straightforward and shouldn't cause too much confusion.

Password handles hashing and salting. Would you need to add a pepper in the mix, you would have to do it yourself.

Now that the User model can handle passwords, it's time to implement what warden is known for: strategies. Strategies are classes that tries to authenticate a session via a method name authenticate! It can either make the authentication process fail! or let the user in by calling success! A strategy can also be skipped if it returns false to the valid? method.

You can create a folder name strategies in app/ and use namespace to encapsulate your strategies. This exercise is left to you, the reader.

Here's a strategy that will check if an e-mail and password match and fail otherwise.


class PasswordStrategy < ::Warden::Strategies::Base
  def valid?
    return false if request.get?
    user_data = params.fetch("user", {})
    !(user_data["email"].blank? || user_data["password"].blank?)
  end

  def authenticate!
    user = User.find_by_email(params["user"].fetch("email"))
    if user.nil? || user.confirmed_at.nil? || user.password != params["user"].fetch("password")
      fail! :message => "strategies.password.failed"
    else
      success! user
    end
  end
end

Warden::Strategies.add(:password, PasswordStrategy)

Note: If you're used to HashWithIndifferentAccess, don't use symbol here. Params is a simple hash. Strategies are called at Rack's level, not inside Rails routing system.

I use the valid? method to skip on this strategy if the request is a GET method or if the params do not include a user to log in.

After your class, you need to add your strategy to Warden::Strategies. Here's why:

In application.rb, we have configured warden to use a failure application but still haven't configured warden to use a default strategy. Let's do this now that we have a strategy to work with.


#application.rb
config.middleware.insert_after ActionDispatch::Flash, Warden::Manager do |manager|
  manager.default_strategies :password
  manager.failure_app = UnauthorizedController
end

Controllers

The sad thing about everything we did is that it's hard to know if everything we've done is going to work or fall apart. Obviously, testing the algorithms would give a sense of things working, but you still can't create a new user and log in. Time to fix this!

Remember at the beginning, we created 3 different routes? Before implementing them, let's add a few helpers that devise usually comes with. These helper methods can be safely added to ApplicationController:


class ApplicationController < ActionController::Base
  protect_from_forgery
  prepend_before_filter :authenticate!

  helper_method :warden, :signed_in?, :current_user

  def signed_in?
    !current_user.nil?
  end

  def current_user
    warden.user
  end

  def warden
    request.env['warden']
  end

  def authenticate!
    warden.authenticate!
  end
end


class SessionsController < ApplicationController
  skip_before_filter :authenticate!
  def create
    authenticate!
    redirect_to :root
  end

  def destroy
    warden.logout
    redirect_to :root
  end
end


class ConfirmationsController < ApplicationController
  skip_before_filter :authenticate!
  before_filter :redirect_if_token_empty!

  def show
     @user = User.where(:confirmation_token => params[:token]).first

    if @user.nil?
      flash.alert = t("confirmations.user.errors")
      redirect_to :root and return
    else
      flash.notice = t("confirmations.user.confirmed")
      @user.confirm!
      warden.set_user(@user)
      redirect_to user_path(@user) and return
    end

  end

  protected

  def redirect_if_token_empty!
    unless params.has_key?(:token)
      flash.alert = t("confirmations.token.empty")
      redirect_to :root and return
    end
  end
end



class RegistrationsController < ApplicationController
  skip_before_filter :authenticate!

  def new
    @user = User.new
  end

  def create
    @user = User.new(params[:user])
    if @user.save
      flash[:notice] = t("registrations.user.success")
      redirect_to :root
    end
  end
end

I didn't include the views because I believe you are capable of creating your own forms & links. If not, there's a lot of resource to help you with that.

While this post is lengthy, the implementation of warden can be done in about 30 minutes. I (maybe) over-detailed the steps but it's because I wanted to make sure nothing was left unanswered.

When you create a new user, a confirmation token will be created. If you type in your browser localhost:xxxx/confirmations?token=confirmation_token, your user will be confirmed and will be able to log in to your site. I wanted to add the mailing process of the confirmation token but this post is long enough as it is. If you would really want to have it explained, drop me a line in the comment and I'll do a follow up post.