---
title: Simple Ruby on Rails Authorization
teaser:
tags: web,ruby,rails
author: Dan Croak
published_on: 2009-08-12
---

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

Routes:

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

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

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.

## Authentication

Users are authenticated using <a
href="http://github.com/thoughtbot/clearance">Clearance</a>. 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 = current_user.account.brands.build
      end

      def create
        @brand = current_user.account.brands.build(params[:brand])
        # ...
      end

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

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
      end
    end

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 = @brand.offers.build
      end
    end

`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
    end

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 <abbr title="Don't Repeat Yourself">DRY</abbr>, and uses normal
authentication and RESTful conventions. It's easy to test and I know where those
tests should go.
