Pothibo

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.

  • Helpers like current_user, user_signed_in?, etc;
  • Default failure application that handles guest / redirection;
  • All the controllers. Don't worry, it sounds worse than it actually is;
  • Many default configuration options.

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:

  • Encrypted password;
  • Confirmation token after registration.

Here's the migration needed for these to works

# Doesn't have to make sense for now. Trust me! 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.

Get more ideas like this to your inbox

You will never receive spam, ever.