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!