Simple Ruby on Rails Authorization

The following describes a simple approach to Ruby on Rails authorization that re-uses the domain model to do the heavy lifting.


resources :accounts, only: [:new, :create, :show]

resources :brands, only: [:new, :create, :show] do |brands|
  brands.resources :offers, only: [:new]

Brands belong to accounts. Offers belong to brands. Users belong to accounts.

I prefer flat routes (and no subdomains) when at all possible. It keeps the mental overhead low everywhere in the app.


Users are authenticated using Clearance. They have a account_id foreign key.

With an authenticated user in a typical “account” application, we can lean on Clearance’s :authorize before filter and ActiveRecord finders.

class BrandsController < ApplicationController
  before_filter :authorize

  def new
    @brand =

  def create
    @brand =[:brand])
    # ...

  def show
    @brand = current_user.account.brands.find(params[:id])

With this pattern, the user is restricted to interacting with brands to which they have access through their account.

Test at the controller level

it 'does not find brands not associated with user' do
  brand = create(:brand)
  sign_in_as create(:user)

  assert_raises(ActiveRecord::RecordNotFound) do
    get :new, brand_id: brand.to_param

Rails returns a 404 when ActiveRecord::RecordNotFound is raised. This error will be raised in our access control scheme because there is no record of the current_user having a relationship to this brand.

Let’s get to green:

class OffersController < ApplicationController
  before_filter :authorize

  def new
    @brand = current_user.brands.find(params[:brand_id])
    @offer =

User belongs to accounts and Account has many brands. I could have said current_user.account but I kept the chain from the perspective of the controller shorter using delegation:

class User < ActiveRecord::Base
  include Clearance::User

  belongs_to :account

  delegate :brands, to: :account

This will make my life easier when the rules around users’ relationship to brands get more complex.

Too lightweight

This authorization approach requires few lines of code and no extra gem dependencies beyond Rails and Clearance. It leans heavily on the framework, stays DRY, and uses normal authentication and RESTful conventions. It’s easy to test and I know where those tests should go.

Pair with one of our expert developers to level up your skills with Coaching by thoughtbot. Save time learning best practices and techniques for reducing technical debt in Ember, Ruby, Haskell, and Go in 1-on-1 sessions tailored to your goals.