Easy Rails Admin Login with Google Apps and OmniAuth
When you're building a web application, there's always the question of how to handle the site-wide administration. You probably have a small list of people at your company that should be able to access it. If you're like Intridea, you also use Google Apps to handle e-mail etc. Using OmniAuth it is trivially simple to set up a simple "admin login system" for your Rails 3 app. Here's how to do it.
Step One: OmniAuth Up and Running
First we'll need to include OmniAuth in our Gemfile. Just add it like
so:
gem 'omniauth'
Next, we should configure OmniAuth for our Google Apps login. Note that
you can add this even if you're already using a different Google Apps
strategy for OmniAuth. Create the file config/initializers/omniauth.rb
and put this in it:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_apps, OpenID::Store::Filesystem.new('/tmp'), :domain => 'yourcompany.com', :name =>
'admin'
end
This makes it so that going to /auth/admin from your application will
have you authenticate through your company's Google Apps account (and
ONLY your company's accounts will work).
Step Two: Routing
Next let's set up our routes for the admin panel. In this case I'm
supposing that you want to scope everything to an 'admin' subdomain and
also that all of your controllers are module scoped to admin (i.e.
Admin::UsersController), but you could easily use namespace to make
it a path prefix instead. In config/routes.rb, add:
match '/auth/admin/callback', :to => 'sessions#authenticate_admin'
constraints :subdomain => 'admin' do
scope :module => 'admin', :as => 'admin' do
root :to => 'users#index'
resources :users
# etc.
end
end
What this has done is created an OmniAuth callback route and a group of
routes that are constrained to the "admin" subdomain and will be
prefixed with admin_ in the named routes. Now that we've got the
routes set up, let's actually add the controllers!
Step Three: Controllers
First we'll want to make the callback active. Generate a "sessions" controller if you don't already have one, and add this code to it:
class SessionsController < ApplicationController
def authenticate_admin
auth_hash = request.env['omniauth.auth']
session[:admin_user] = auth_hash['user_info']['email']
if admin?
redirect_to '/'
else
render :text => '401 Unauthorized', :status => 401
end
end
end
What we do here is take the e-mail that is provided to us by OmniAuth
and store it in the session as the :admin_user. We then check to
see if that e-mail has admin priveleges (using a method we'll write
in just a moment) and redirect them to the root of the admin section
if so. Next we need to add the admin? helper to
ApplicationController:
class ApplicationController
def admin?
session[:admin_user] && (ENV['ADMINS'] || "").split(',').include?(session[:admin_user])
end
helper_method :admin?
def admin_required
redirect_to '/auth/admin' unless admin?
end
end
This checks to see if our :admin_user is in a comma-separated list of
approved administrators set at the environment level (a useful strategy
for Heroku deployments, but you could do lots of things here such as
load a list from YAML etc.). We've also written a method that will be
used as a before_filter to require admin login.
Next up let's actually make our protected resources! If you don't already have it, generate a user admin controller:
rails g controller admin/users
Next up let's just add a simple action to make sure it works:
module Admin
class UsersController < ApplicationController
before_filter :admin_required
def index
render :text => 'Hello from the admin panel!'
end
end
end
With that, you're done! When you go to admin.yourapp.com you should be
redirected to log in using Google Apps. Once you've done that, if your
name is on the comma-separated list you will be authorized to access the
protected controller. This is a simple method that is independent of
database storage so can be very useful in just about any application.
When OmniAuth makes it so easy, there's no reason not to make your
administrative login as smooth and painless as possible!