02 Apr 2011

Mount Resque Interface in Rails 3 and Restrict Access with Authlogic

Resque is a super-useful Rails gem which allows you to queue long-running tasks for background processing. It uses Redis as its data store, ensuring fast access but also data security in case the machine goes down.

It also comes with a really nice front-end interface that allows you to see your queues and workers and retry jobs that have failed. This is packaged with the gem as a Sinatra app. In Rails 3, you can mount Rack apps as subdirectories, so ideally we’d like to have our Resque interface available at, say, http://myapp.com/resque. The way to do this has been covered already.

But what if you’d like to use something other than kludgey basic auth? We’re already using Authlogic and ActiveRecord in the rest of our app to authenticate users and track privileges, so we’d like to authenticate to the Resque interface using the same system. We won’t allow people to log in through the Resque interface, but we can certainly restrict access to logged-in users by sharing the session cookie between our Rails app and the Resque Server.

Here’s how to do it:

Make sure Authlogic works in your Rails 3 app. Unfortunately Authlogic looks to be sort of an abandoned project. The official 2.1.6 version of the gem doesn’t work for me and throws deprecation warnings, so I’m using the 'rails3' branch of odorcid's fork on Github. The line in the Gemfile is:

gem 'authlogic', :git => 'git://github.com/odorcicd/authlogic.git',
  :branch => 'rails3'

In your routes file, place this line of code:

mount Resque::Server.new, :at => "/resque"

Then, in config/initializers/resque.rb, place these lines (jump down for explanation):

require 'resque/server'

begin
  # this will fail because it has some bad inclusion code
  require 'authlogic/controller_adapters/sinatra_adapter'
rescue NoMethodError
end

module Resque
  class Server

    configure do
      enable :sessions
    end

    use Rack::Session::Cookie,
      :key => 'MY_SESSION_KEY',
      :secret => 'MY_SESSION_SECRET'

    def current_user_session
      return @current_user_session if defined?(@current_user_session)
      @current_user_session = UserSession.find
    end

    def current_user
      return @current_user if defined?(@current_user)
      @current_user =
        current_user_session && current_user_session.record
    end

    before do
      controller =
        Authlogic::ControllerAdapters::SinatraAdapter::Controller
          .new(request, response)
      Authlogic::Session::Base.controller =
        Authlogic::ControllerAdapters::SinatraAdapter::Adapter
          .new(controller)

      redirect '/login' unless
        current_user && current_user.admin_privileges?
    end

  end
end

The first thing we need to do is require the Resque Server code and the Authlogic Sinatra bridge (Resque Server is a Sinatra app). Unfortunately, the Authlogic Sinatra bridge tries to add behavior to a method in the wrong class when it’s included, so we need to catch this and fail silently.

Next, we’ll reopen the Resque::Server class to add some functionality.

We configure the Resque Server app to use sessions. We then provide the same session key and secret as are specified in session_store.rb and secret_token.rb. In this case, we’re using a cookie-based session store.

Next, we add the current_user and current_user_session helper methods to our Resque Server class. Basically, this class serves the same purpose as ApplicationController in a Rails app. Next, we must make sure that Authlogic gets bridged with our Sinatra ‘controller.’ This is the code that should have been added automatically for us when we included the Sinatra bridge file.

Lastly, we add some logic in the before method to check that we have a logged-in user and that this user can access the Resque Server interface. The before method is similar to Rails’ before_filter in that it runs before any actions do. Thus, we can redirect the user to a login page (or somewhere else) if they do not have access privileges.