AuthorizedController

AuthorizedController

We use a few Rails engines such as High Voltage and Clearance which allow you to override default behaviors of controllers by subclassing them like:

class SessionsController < Clearance::SessionsController
  layout 'public-facing'
end

Meanwhile, we often work on apps which require most of the controllers to be authorized. If we wanted to have a home page that uses High Voltage, one way we might do it is:

class ApplicationController < ActionController::Base
  include Clearance::Authentication # or other authentication mechanism
  before_filter :authorize
end

class PagesController < HighVoltage::PagesController
  skip_before_filter :authorize
end

MyApp::Application.routes.draw do
  # all the authorized routes
  resources :pages
end

It’s annoying to have a new route plus a new controller and to define and override a before_filter. Meanwhile, the authorization logic is “hidden” in ApplicationController.

Another way is:

class ApplicationController < ActionController::Base
  include Clearance::Authentication
end

class CommentsController < ApplicationController
  before_filter :authorize
end

For each controller, you call the before_filter. It’s declarative in the sense that when you read the CommentsController class, you can see it’s protected by authorization. The downsides are:

  • you have to remember to write it for each controller
  • you have to write some custom authorization specs for the controller

Another way is:

class ApplicationController < ActionController::Base
  include Clearance::Authentication
end

class AuthorizedController < ApplicationController
  before_filter :authorize
end

Now, we subclass from AuthorizedController for only those controllers that need the functionality.

class CommentsController < AuthorizedController
  # ...
end

Again, it’s declarative, we can read the class and know it’s authorized. Again, it requires we remember, which means we might forget.

I’d argue that if we TDD the controller, we shouldn’t forget.

The advantage of subclassing from the intermediate AuthorizedController over the before_filter :authorize in each controller style means we can feel confident with a spec like:

describe CommentsController do
  it { should be_kind_of(AuthorizedController) }
end

That assumes we have a spec somewhere like:

class MocksController < AuthorizedController
  def show
  end
end

describe MocksController do
  before do
    MyApp::Application.routes.draw do
      resource :mock, :only => [:show]
      match 'sign_in' => 'sessions#new', :as => :sign_in
    end
  end

  after do
    Rails.application.reload_routes!
  end

  context "a visitor" do
    it "is denied access to blank slate" do
      get :show
      should redirect_to(sign_in_url)
    end
  end
end

If this style gains a little support, we could put AuthorizedController and this spec into Clearance itself so it wouldn’t need to be hanging around in each of our app’s codebases.

I feel like it’s the right amount of work, with the right amount of test coverage, without extraneous subclassing and routes.

Which approach do you prefer? Something different?

Dan Croak Developer