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"
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
secret_token.rb. In this case, we’re using a cookie-based session store.
Next, we add the
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.