Shared Auth for Rack Apps September 26th, 2011
Being able to mount rack apps inside each other is awesome.
For example, Resque
provides Resque::Server
for monitoring job processing and
that means I wont have to write an interface for it myself.
Great, /admin/resque
is going to be what I want with
practically no work on my part. But since I'm using these
for real in the admin area of
Bugrocket – there's a
problem with just mounting apps under /admin
. The
parts that I wrote myself use my authentication
layer — and they make sure you're actually an administrator!
I could wrap Resque::Server
in some other rack app I write,
which would duplicate the logic and require the user to
login again. Or maybe use HTTP basic auth... not much better.
Instead I've pieced together some ideas I found online (like this slideshare presentation) and have a method of writing authentication once with Warden and sharing it easily with other rack apps.
Note
This is written from the perspective of mounting
micro-apps like Resque::Server
and
Route53::Web
inside a Rails 3.X app. Warden is pretty flexible though,
so you could conceivably do this with any rack app
as the parent.
Setup
Mount your micro-app wherever as above.
Add
warden
to yourGemfile
:At the time of writing
rails_warden
from rubygems was giving me strange errors I didn't have time to look into. If you have trouble just use the git repo as the source:
:git => 'git://github.com/hassox/rails_warden.git'
Configuration
config/initializers/warden.rb
is where we'll make this all happen.
First we tell Warden the strategies we plan to use. :admin
is
an arbitary name – it's what I'm going to be authenticating.
Then you can specify whichever controller you want the user to be
redirected to when they need to login. In my case I have
a sessions controller in the Admin
namespace, pretty typical.
Next you need to define some hooks Warden will use to get and persist the current user to the session:
Again pretty typical.
Authentication Strategy
Now we get into the meat. We need to define what the :admin
strategy
we mentioned earlier actually does to authenticate a user.
The authenticate!
method is the logic that used to be part of
a helper in my actual admin controllers. We're moving it here so it
can be shared.
Wiring It Up
So at this point we can already go ahead and use this code for authenticating in the parent app, replacing all the auth code we used to have written directly with the new Warden-backed implementation.
You probably have a helper somewhere, something like this:
Well now we can just change it to:
Similarly, we can just call the same warden.authenticate! :admin
in our Admin::SessionsController#create
method and no longer
need to write the authentication logic in our controller
– everything is handled by Warden.
Mounted Apps
So assuming all of that is working you have successfully replaced your inline authentication, but nothing has really changed. Users still wont be asked to login when visiting mounted apps.
But this last step is super easy. We just define an authentication middleware:
This looks a bit fiddly but there's nothing to it. Basically
we just transplant our Rails app's session configuration
into rack.session.options
, because that's where rack apps are
expecting to see session info. Warden conveniently places
a lazy-loaded instance into env
for us, we call it
just like we are in our regular admin controllers, and
we receive many fresh sandwiches.
The last bit is just telling our mounted app(s) to use the new middleware.
Demo
Take a look at an example app that demonstrates all of this stuff.