DEV Community

Lucas Barret
Lucas Barret

Posted on

ActionPolicy , GraphQL and Rails

In the last episode of GemRubyShow, I had the opportunity to discuss Authorization with Vladimir Dementyev.
I wanted to create an article showing you how simple yet powerful ActionPolicy the Gem he develops for Authorization.

ActionPolicy was meant to be a part of Rails. This is why it is called Action Policy. If you want to know the difference between Action and Active, we discuss that in the podcast with Vladimir.

Even if ActionPolicy is Rails agnostic, this article will focus on implementing ActionPolicy in a simple Rails app using GraphQL.

Authorization

I tweeted about authorization some time ago to remind myself of something important.

Nevertheless, what is Authorization? Let's try to answer that here :)

As it is said in the ActionPolicy documentation : Authorization is the act of giving someone official permission to do something.

Moreover, there are several models of authorization for the most famous we have :
- DAC for Discretionary Access Control
- MAC for Mandatory Access Control
- RBAC for Role-Based Access Control
- ABAC for Attribute-Based Access Control

Vladimir dive into these different models in his conference of 2018

Authorization in GraphQL without ActionPolicy

Let's say you have your Rails app with GraphQL installed. If you want to use authorization, you can use the graphql-ruby authorized hook.

Let's say you have a mutation query and want to ensure that the current_user is authorized to perform this action, for example.
For example, let's take a mutation that triggers a computation job that uses... I don't know GoodJob, for instance.
If you want to perform authorization, you will do something that looks like this :

module Mutations 
    class TriggerJobMutation < Mutations::BaseMutation

        field :status, Boolean, null: false

        def authorized?
            current_user.admin?
        end

        def resolve(*)
            MyJob.perform_later
            {status: true}
        end
    end
end
Enter fullscreen mode Exit fullscreen mode

First, we have put the current_user in the context in the GraphQL controller (you can uncomment the line in the execute method).

Here, your authorized hook will be executed before the resolve method, and if it returns false at the end, the resolve method will not be executed.

After that, you will add the method to your mutation_type like this :

#app/graphql/types/mutation_type.rb

field :trigger_job_mutation, mutation: Mutations::TriggerJobMutation
Enter fullscreen mode Exit fullscreen mode

And this is over... But as you can imagine, and as Vladimir said in his conference, this could be a bit better...

Indeed, you will end up rewriting things everywhere. And we feel that it is not scalable.

Let's see how ActionPolicy can help us avoid this cumbersome work.

Call the Police?

First, install the ActionPolicy gem in our Rails project bundle add action_policy-graphql. And then install it; it is pretty standard with a gem: bin/rails g action_policy:install

Using our previous example, You can create a policy that checks if your current_user is an admin.

By default, action_policy-graphql uses the current_user defined in the context. Though remember to be sure you have a current_user in your context.

class TriggerJobPolicy < ApplicationPolicy
  def trigger?
    current_user.admin? 
  end
end
Enter fullscreen mode Exit fullscreen mode

If you want to add an authorization layer to this mutation, in your mutation_type.rb, add preauthorize to your field, and you are good to go.

Why preauthorize and not authorize? After playing with the gem (or reading the doc :)). You can learn that authorize will execute the query and then check the authorization.

We want something else since if the user is not authorized to act, you want to prevent the action from being completed.

To avoid that, you have the preauthorize hook, which will check the authorization before performing your mutation.

#app/graphql/types/mutation_type.rb
field :trigger_job_mutation, mutation: Mutations::TriggerJobMutation, preauthorize: {to: :trigger?, with: TriggerJobPolicy}
Enter fullscreen mode Exit fullscreen mode

And that's it, I swear this is dead simple!

Now, you have to test your app and your authorization layer.

Testing Authorization

Now, you want to test that your authorization is correctly set up. Nothing more simple, ActionPolicy brings helpers that you can add to your rails_helper.rb. This includes matters for simplifying your test code.

If you use RSpec, in your rails_helper.rb file, you can add require 'action_policy/rspec'.

# frozen_string_literal: true
require 'rails_helper'

describe do
  describe 'authorization' do
      let(:mutation) do
        %(mutation {
            triggerJobMutation(input: {}) {
              status
            }
        })
      end
      let(:user) { User.create!(name: 'Lucas') }
      subject do
        TestgoodjobSchema.execute(mutation, variables: {}, context: { current_user: user })
      end
      it 'is authorized' do
        expect { subject }.to be_authorized_to(:trigger?, 'triggerJobMutation').with(TriggerPolicy)
      end
    end
end
Enter fullscreen mode Exit fullscreen mode

We are production-ready now, right ?

That's all ?

The actual strength of ActionPolicy lies in the multiple possibilities that another gem does not offer.

Like caching, performance can be a huge concern.
With ActionPolicy, you have caching at several levels (Instance, Thread, Cache Store). Moreover ActionPolicy uses PORO, which enables namespaces and is flexible.

ActionPolicy is like a supercharged authorization gem and will ease your development. In this article, we barely scratched the surface of its capability. Yet, as we have seen, ActionPolicy is easy to use and straightforward.

Top comments (0)