Protect your GraphQL data with resource_policy
Tags: ruby, rails, patterns, authorization
Published: 2023-02-20 04:41:46

GraphQL is a query language for APIs that was created by Facebook. It provides a more efficient, powerful, and flexible alternative to REST APIs. One of the main advantages of GraphQL is that it allows clients to request exactly the data they need, and nothing more.

In addition to its flexibility, GraphQL also provides an excellent way to handle authorization and access control. Authorization is the process of ensuring that a user has the proper permissions to perform an action, and access control refers to the methods and policies that regulate which users can access specific resources or data.

In this article, we will discuss how to integrate GraphQL with [resource_policy](https://github.com/samesystem/resource_policy), a Ruby gem for attribute-level authorization, to create fine-grained access control for your API. We will cover the basic concepts of [resource_policy](https://github.com/samesystem/resource_policy), how to use it to protect your resources, and how to expose your policies through GraphQL to provide your API users with visibility into which data they can access.

## Two layers of protection

If you are interested in the authorization levels concept, I recommend checking my previous article titled [Attribute level permission control in Rails](https://dev.to/povilasjurcys/method-level-permission-control-in-rails-4fl5) where I delved into the topic of authorization and its two layers - action and attribute. In this article, I will focus on the implementation.

In simple terms, action authorization is a straightforward approach where a user's request is either accepted or rejected based on their role or other conditions. This approach is implemented in a controller through a simplified code such as:

```ruby
class UsersController < ApplicationController
  before_action :require_admin, only: [:create]

  def show # everyone can do this action
    # ...
  end

  def create # only admins can do this action
    # ...
  end
end
```

However, in attribute-level authorization, the system does not reject the request but rather returns only part of the requested data. For instance, different response bodies may be returned based on the user making the request.

```js
// user(id: 1) response for admin
{
  "data": {
    "id": 1
    "email": "john@example.com"
  }
}

// user(id: 1) response for non-admin
{
  "data": {
    "id": 1
    "email": null // email is not authorized to see
  }
}
```

Attribute-level authorization is more complex, but it is essential in more intricate applications. Luckily, there are tools like the [resource_policy](https://github.com/samesystem/resource_policy) gem that make it easier to achieve this type of authorization.

## What is resource_policy?

If you're looking for an easy way to configure attribute-level authorization for your Rails application, look no further than [resource_policy](https://github.com/samesystem/resource_policy). Based on the PolicyObject pattern, this gem offers several methods to make configuration simple.

In a PolicyObject class, for example, you can use the policy method to specify which attributes are allowed for which actions. In the following example, the id attribute can be read, and the email attribute can be read only if the current user is an admin:

```ruby
class UserPolicy
  include ResourcePolicy::Policy

  policy do |c|
    c.attribute(:id).allowed(:read)
    c.attribute(:email).allowed(:read, if: :admin?)
  end

  def initialize(user, current_user)
    @user = user
    @current_user = current_user
  end

  def admin?
    @current_user.admin?
  end
end
```

One of the benefits of [resource_policy](https://github.com/samesystem/resource_policy) is that it's easy to integrate with other gems like a [pundit](https://github.com/varvet/pundit), so you don't have to rewrite everything. If you're looking for a straightforward way to implement attribute-level authorization, [resource_policy](https://github.com/samesystem/resource_policy) is definitely worth considering.

## resource_policy and GraphQL

If you're using GraphQL and want to protect your data with a simple configuration, [resource_policy](https://github.com/samesystem/resource_policy) provides a method called `protected_resource`. This method wraps your record and returns nil for every unauthorized attribute, making it easy to protect your data with a few lines of code.

For example, consider the following code:

```ruby
current_user.admin? #=> false

user = User.find(1337)
user.id #=> 1337
user.email #=> "john.doe@example.com"

policy = UserPolicy.new(user, current_user)
protected_user = policy.protected_resource
protected_user.id #=> 1337
protected_user.email # nil
```

In this example, protected_resource returns a user record with only the authorized attributes, which is useful for protecting data in GraphQL. With a few lines of code, you can configure [resource_policy](https://github.com/samesystem/resource_policy) to protect your data in GraphQL:

```ruby
class Types::QueryType < GraphQL::Schema::Object
  field :users, [UserType], 'Returns protected users'

  def users
    current_user = context[:current_user]
    Users.all.map do |user|
      UserPolicy.new(user, current_user).protected_resource
    end
  end
end
```

By using `UserPolicy.new(user, current_user).protected_resource`, you can wrap the user record with the policy and return only the authorized attributes to the client. This makes it easy to add attribute-level authorization to your GraphQL API with minimal effort.

## Querying policies itself

Expressing authorization rules can be a bit challenging with the use of other authorization gems, such as [pundit](https://github.com/varvet/pundit) or [cancancan](https://github.com/CanCanCommunity/cancancan). The [resource_policy](https://github.com/samesystem/resource_policy) gem provides a more concise and expressive policy definition that uses a simple block-based syntax that makes it easy to understand and write authorization rules for each attribute.

One of the benefits of [resource_policy](https://github.com/samesystem/resource_policy) is that you have easy access to the protected attributes list through user_policy.attributes_policy.all_allowed_to(:read). This feature provides fine-grained control over attribute access by specifying different access levels, such as :read or :write.

With these features, [resource_policy](https://github.com/samesystem/resource_policy) is a great option if you want to expose policies through GraphQL or an API. Exposing policies is important because it helps the frontend or any other heavy GraphQL user know which data they can access before making any real request. To give an idea, GraphQL can look like the following query:

```graphql
query userPolicy(id: 123) {
  email {
    isReadable
    isWritable
  }
    
  name {
    isReadable
    isWritable
  }
}
``` 

This query returns something like the following, based on the policy result:

```js
{
  "data": {
    "userPolicy" {
      "user" {
        "email": {
          "isReadable": true
          "isWritable": false
        },
        "name": {
          "isReadable": true
          "isWritable": true
        }
      }
    }
  }
}
```

This way, the GraphQL user can decide whether to render input fields that are read-only or skip making requests altogether.

## Implementation details

You might be wondering how to implement [resource_policy](https://github.com/samesystem/resource_policy) with GraphQL. While the library does not come with built-in GraphQL support, it can be easily integrated with GraphQL using a few lines of code.

To get started, you'll need to create an AttributePolicyType that exposes the access levels for each attribute. In the simplest case, you might expose only read and write levels:

```ruby
class AttributePolicyType < GraphQL::Schema::Object
  field :is_writable, GraphQL::Types::Boolean, property: :writable?
  field :is_readable, GraphQL::Types::Boolean, property: :readable?
end
```

You'll also need to create a policy type for each model. For example, you can create a UserPolicyType to expose the data from the UserPolicy:

```ruby
class UserPolicyType
  UserPolicy.policy.attributes.each_key do |attribute|
    field attribute, AttributePolicyType
  end  
end
```

This class defines attributes dynamically based on the policy, so you don't need to hard-code them.

Finally, you can update the Query type to make the policy accessible via GraphQL:

```ruby
class Query < GraphQL::Schema::Object
  # ...
  field :user_policy, UserPolicyType do
    argument :id, ID
  end

  def user_policy(id:)
    user = User.find(id)
    UserPolicy.new(user, context[:current_user])
  end
end
```

Now you can query the policy using query `userPolicy(id: 1) { ... }` to find out which attributes are accessible. This can be useful for front-end developers who want to know which data they can access before making any requests.

## Final thoughts

In conclusion, [resource_policy](https://github.com/samesystem/resource_policy) is a powerful and flexible gem that can help you easily implement attribute-level authorization in your Ruby on Rails application. Its concise and expressive policy definition, easy access to protected attributes, and fine-grained control over attribute access make it a strong contender for exposing policies via GraphQL or API.

By following the steps outlined in this article, you can create an `AttributePolicyType` and `UserPolicyType`, and update your `Query` GraphQL type to expose your policies. This will enable your GraphQL users to know which data they have access to before making any real requests, thus helping them make better decisions and reduce unnecessary network traffic.

While [resource_policy](https://github.com/samesystem/resource_policy) does not have out-of-the-box GraphQL support, it can be easily integrated with GraphQL with just a few lines of code. With [resource_policy](https://github.com/samesystem/resource_policy), you can ensure that your application's data is protected and accessible only by authorized users, providing a more secure and reliable user experience.