Controlling access to Routes and Rack apps in Rails 3 with Devise and Warden

Devise is a great authentication solution for use within your Rails apps. We’ve used it in a number of projects and have always been happy with the flexibility but never realised quite how great it was until very recently.

Recently we wanted to make use of an A/B testing solution and one of the options was Split. Like a number of projects nowadays (Resque included) Split comes with an embedded server that allows you to control it via the web interface.

You can mount that rack app using:

mount Split::Dashboard, :at => 'split'

The problem is this leaves split open for anyone to see and your AB testing is open for anyone to play with. The split guide recommends that you can use a standard rack approach to protect your split testing.

Split::Dashboard.use Rack::Auth::Basic do |username, password|
  username == 'admin' && password == 'p4s5w0rd'

We were already using Devise though and I’m sure this is only there to prove as an example by the authors. What we really wanted to do was use Devise to protect our mounted app. It turns out that this isn’t that difficult. Firstly you can authenticate any route using devise using the authenticated command like the following:

authenticated(:user) do
  resources :post

This ensures that the following route, specified with the block, is only availible to people who have authenticated. Using authenticate you can also bounce people to the login page if they’re not authenticated:

authenticate(:user) do
  resources :post

This is awesome and ensures that only registered users can access the routes. You can of course replace :user with any of your scopes, such as :admin, in order to ensure that only specific scopes have access to the route. However, we wanted a bit more control. What if you want to only allow any users that had the boolean `allowed_to_ab_test?` method?

As soon as you realise that:

mount Split::Dashboard, :at => 'split'

is actually just a short hand for

match "/split" => Split::Dashboard, :anchor => false

You can use any normal constraint. Since warden makes use of the request object you can pull out any details you like from warden including the user model by using a lambda for the constraint. This provides massive flexibility for controlling your routes:

match "/split" => Split::Dashboard, :anchor => false, :constraints => lambda { |request|
  request.env['warden'].authenticated? # are we authenticated?
  request.env['warden'].authenticate! # authenticate if not already
  # or even check any other condition

With that example we can take a really fine grained control of our routes and only allow access to certain routes for certain people without having to specify different scopes for the users.

Nothing here’s groundbreaking but it took us a little time to figure it out. The combination of Devise and Warden gives a huge amount of power and flexibility allowing you to protect both your normal routes or your embedded apps. We thought we’d share this in case it was useful to others. It’s certainly opened our eyes to just how great Devise and Warden are!