ActionPolicy , GraphQL and Rails
Tags: ruby, rails
Published: 2023-09-12 10:12:26

In the last [episode](https://www.youtube.com/watch?v=K9wgJI-Dbic) 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.

{% twitter 1696139578694857055 %}

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

As it is said in the ActionPolicy [documentation](https://actionpolicy.evilmartians.io/#/) : `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 :

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


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 : 

```ruby
#app/graphql/types/mutation_type.rb

field :trigger_job_mutation, mutation: Mutations::TriggerJobMutation
```

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.

```ruby
class TriggerJobPolicy < ApplicationPolicy
  def trigger?
    current_user.admin? 
  end
end
```

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.

```ruby
#app/graphql/types/mutation_type.rb
field :trigger_job_mutation, mutation: Mutations::TriggerJobMutation, preauthorize: {to: :trigger?, with: TriggerJobPolicy}
```

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'`.

```ruby
# 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
```

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.