Don't skip authorization layer (and use Action Policy)

Table of contents

When I started Business Class I wanted to do as few choices for the user as possible. I didn't want to discourage people that might prefer an alternative library. One result of that was that I haven't included an authorization layer of any kind. But version 2.0 changed that and I built a proper authorization layer with Action Policy.

No authorization?

Saying we didn't have authorization might not be 100% accurate, but it was treated as authentication. Just a before action filter to check if the user is an admin or not. And honestly that's kind of enough for what's in the template. But as you'll build your application you are probably going to want to have something better.

One such place was the generator. I was significantly improving the CRUD generator and it felt wrong not to already include authorization handling. Why? Because I wanted to generate files you can just commit to source control. Something already complete. I didn't want to generate something only to go and update it immediately. Same with the tests.

Action Policy

So I had a look around on what might be the best authorization library of the day. I realized that I had starred Action Policy on GitHub and gave it a deeper look. And although I never used it at work, I immediately liked it.

Action Policy is basically Pundit, but better. It has very little magic and comes with GraphQL integration. Funny enough, I don't like GraphQL and try to stay away from it. But if I'll need to use GraphQL it's better if it's with a policy framework that can work with it.

Here's a example policy file generated by Business Class:

class BlogPolicy < ApplicationPolicy
  def update?
    space_owned? || user&.admin?
  end

  def destroy?
    # deny! unless record.deletable?
    update?
  end
end

A minimal policy that let's the tenant users to modify the blog, as well as allowing an administrator to do so. Note that Space model is the tenant and can be renamed to what you want (Tenant , Team ). And here's the test:

require "test_helper"

class BlogPolicyTest < ActiveSupport::TestCase
  setup do
    @record = blogs(:one)
    @space = spaces(:owners)
    @unauthorized_space = spaces(:freelancer)
    @admin = users(:chris)
    @owner_policy = BlogPolicy.new(@record, space: @space)
    @unauthorized_policy = BlogPolicy.new(@record, space: @unauthorized_space)
    @admin_policy = BlogPolicy.new(@record, user: @admin)
  end

  test "#update?" do
    assert @owner_policy.apply(:update?)
    assert_not @unauthorized_policy.apply(:update?)
    assert @admin_policy.apply(:update?)
  end

  test "#destroy?" do
    assert @owner_policy.apply(:destroy?)
    assert_not @unauthorized_policy.apply(:destroy?)
    assert @admin_policy.apply(:destroy?)
  end
end

This little policy is now ready to use and very easy to update to whatever is needed.

Integration

You might be wondering how the actual code using the policy looks like. So here's the authorize! method in the controller:

class BlogsController < SpaceScopedController
  # ...

  # GET /spaces/:space_slug/blogs/1/edit
  def edit
    authorize! @blog, to: :update?
  end

  # ...

  # GET /spaces/:space_slug/blogs/1/preview_destroy
  def preview_destroy
    authorize! @blog, to: :destroy?
    render "preview_destroy", layout: false
  end

  # DELETE /spaces/:space_slug/blogs/1
  def destroy
    authorize! @blog
    @blog.destroy!
    redirect_to space_blogs_path, notice: "Blog was successfully destroyed.", status: :see_other
  end

  # ...
end

If the policy method is matching the action name, all works automatically, otherwise we have to provide the to: argument. In a way, Business Class doesn't try to do too much, but it's a time saver to start with policy files in place.

Also everything else in the template was upgraded to use Action Policy for consistency.

Future

I talked about rethinking every single decision for the future version 3.0, but I am confident that Action Policy will stay. It's haven't faced any issues with the library and it's nice put all policy rules to a single place.


© Business Class Blog