Exposing permissions in GraphQL APIs with Action Policy
Tags: showdev, ruby, graphql, authorization
Published: 2019-05-21 16:19:35

---
title: Exposing permissions in GraphQL APIs with Action Policy
published: true
canonical_url: https://evilmartians.com/chronicles/exposing-permissions-in-graphql-apis-with-action-policy
description: Add authorization-related fields to GraphQL schema using Action Policy to "tell" clients which actions are allowed and not
cover_image: https://thepracticaldev.s3.amazonaws.com/i/yh38mm1e4nm2j4eyhgcj.png
tags: showdev, ruby, graphql, authorization
---

Today I'm "celebrating" the anniversary of my life with GraphQL in production Rails apps–**1 year** full of trials and errors, misses and hits, _wtf_-s and [successes](https://dev.to/evilmartians/active-storage-meets-graphql-direct-uploads-3n38).

![](https://thepracticaldev.s3.amazonaws.com/i/ec54litfkgkgpw527q8i.png)

As an open-source addict, I especially loved working with GraphQL: it's a rather young technology, and hence there is a lot of opportunities for contributions (at least, in Ruby world) and experiments.

And now I'm presenting the result of one such experiment–the [`action_policy-graphql` gem](https://github.com/palkan/action_policy-graphql), which glues together [GraphQL Ruby](https://graphql-ruby.org) and [Action Policy](https://actionpolicy.evilmartians.io/) authorization library.

## Wait, Action Policy, was ist das\*?

_\* "what is it" in German (I'm listening to Rammstein while writing this post 🎸)_

About the same time as I've started working with GraphQL, I've presented a new Ruby authorization library, [Action Policy](https://actionpolicy.evilmartians.io/), to the world ([at RailsConf 2018](https://www.youtube.com/watch?v=NVwx0DARDis)).

Action Policy has been extracted from multiple projects I've been working on in the last few years. It's ideologically similar to [Pundit](https://github.com/varvet/pundit) (and initially was built on top of it) but provides a bunch of additional features out-of-the-box (and has a very different architecture _inside_).

One of these features is an ability to provide an additional context on _why_ the authorization check failed–[**failure reasons**](https://actionpolicy.evilmartians.io/#/reasons) tracking.

This feature has been a dark horse for a long time, we barely used it (mostly for debugging purposes) until we started working with GraphQL–that's when the ugly duckling turned into a beautiful swan.

> To learn more about Action Policy and its features check out the slides from the most recent talk I gave at [Seattle.rb](http://www.seattlerb.org) early this year: https://speakerdeck.com/palkan/seattle-dot-rb-2019-a-denial.

## Authorization & GraphQL

> _Authorization_ is an act of giving someone official permission to do something (not to be confused with [authentication](https://en.wikipedia.org/wiki/Authentication)).

Every time we "ask" in our code, "Is a user allowed to do that?" we perform the act of authorization.

When dealing with GraphQL, this question could be divided into more specific ones:
- Is a user allowed to interact with the object (of a specific type or in a particular node of the graph)?
- Is a user allowed to perform this mutation or subscribe to this subscription?

Another related aspect is _data scoping_: filter collections according to the user's permissions instead of checking every single item.

The `graphql` gem provides basic [authorization support](https://graphql-ruby.org/authorization/authorization.html) (via the `#authorized?` hook) and [scoping support](https://graphql-ruby.org/authorization/scoping.html) (via the `#scope_items` hook): it can be good enough in the beginning but doesn't scale well due to the amount of boilerplate.

That's why I've started the development of [`action_policy-graphql` gem](https://github.com/palkan/action_policy-graphql)–to provide a better API for fields authorization and scoping (similar to the one you can see in [GraphQL Pro](https://graphql.pro) for `pundit` and `cancancan`). Now we describe our APIs like this:

```ruby
# field authorization example (`authorize: true`)
field :home, Home, null: false, authorize: true do
  argument :id, ID, required: true
end

# data scoping using policies (`authorized_scope: true`)
field :events, EventType.connection_type, null: false, authorized_scope: true
```

This implementation uses [_field extensions_](https://graphql-ruby.org/type_definitions/field_extensions.html) internally, which turned out to be more flexible than building on top of the `#authorized?` and `#scope_items` hooks.

The problem seemed to be solved: it became effortless to use the authorization and scoping rules defined in policy classes in the API to check permissions and raise exceptions (_"Access Denied!"_). Though the latter one–raising exceptions–wasn't the best way to inform users about the lack of permissions.

What about preventing these exceptions and **_telling_ frontend clients, which actions are permitted and not**?

## Let the client know about its rights

The problem of [pushing current user's permissions](https://github.com/palkan/action_policy/issues/69) information _down_ to the client (frontend/mobile application) is not new; it existed years before GraphQL was born.

The problem could be translated into the following question: _how to make the client know which actions are allowed to a user (and show/hide specific buttons/links/controls)_?

How to solve it?

You may try, for example, to pass only the _authorization model_ (role, permissions set) and implement (i.e., duplicate) the authorization rules client-side. Such duplication is the easiest way to shoot yourself in the foot.

> **You should rely on a single source of _authorization_ truth**.

And this single source of truth is, usually, a **server** (because that's where you perform the authorization checks themselves).

Thus, we need to transform our policy rules to client-compatible format–for example, JSON object.

Dumping all the possible authorization rules for a user into a JSON object doesn't seem to be a good idea, especially if we have dozens of policy classes. Hopefully, one of the GraphQL advantages is **the ability to request only the data you need** (and avoid _overfetching_).

So, we started with the simple idea of adding `canDoSmth` boolean fields to our GraphQL types:

```ruby
class EventType < Types::BaseType
  # include Action Policy helpers (`authorize!`, `allowed_to?`)
  include ActionPolicy::Behaviour

  field :can_destroy, Boolean, null: false

  def can_destroy
    # checks the EventPolicy#destroy? rule for the object
    allowed_to?(:destroy?, object, context: {user: context[:current_user]})
  end
end
```

Now a client can _ask_ the server whether it's allowed to delete the event:

```graphql
query {
  event(id: $id) {
    canDestroy # true | false
  }
}
```

We also added a _macro_ to define authorization fields to reduce the boilerplate:

```ruby
class EventType < Types::BaseType
  include ActionPolicy::Behaviour

  expose_authorization_rules :destroy?, :edit?, :rsvp?
end
```

_"Are we there yet?"_. Nope.

## Clients need more context

It turned out that in most cases knowing whether the action is allowed or not (`true` or `false`) is not enough: we also need to show a notification to the user (or answer the question "Why?"). And in some situations, this message should be different depending on the reason why we disallowed this action.

Consider a simplified example of a rule checking whether a user is allowed to RSVP to the event:

```ruby
def rsvp?
  allowed_to?(:show?) &&  # => User should have an access to this event
  rsvp_open? &&           # => RSVP should be open
  seats_available?        # => There must be seats left
end
```

We're most interested in the last two checks (because if a user has no access to the event, it shouldn't ask for permission to RSVP).

When RSVP is closed, we want to show the _"RSVP has been closed for this event"_ message; when no more seats available–_"This event is sold out."_

How can we get this information from our policy? Using the [_failure reasons_](https://actionpolicy.evilmartians.io/#/reasons) functionality!

We need to change our rule a bit for that:

```ruby
def rsvp?
  allowed_to?(:show?) &&
  # wrapping method call into a `check?` method
  # tracks the failure of this check (i.e. when it returns false) 
  check?(:rsvp_open?) &&
  check?(:seats_available?)
end
```

Now we can retrieve the additional context from the authorization check result:

```ruby
policy = EventPolicy.new(record: event, user: user)
# first, apply the rule
policy.apply(:rsvp?)
# now we can access the result object and its reasons
# for example, details hash contains the checks names grouped
# by a policy
policy.result.reasons.details #=> {event: [:rsvp_open?]} or {event: [:seats_available?]}
```

Wait? Details hash? Identifiers? We need human-readable messages!

OK. Here come the Action Policy and [i18n integration](https://actionpolicy.evilmartians.io/#/i18n).

Let's add our message to the locales files:

```yml
en:
  action_policy:
    policy:
      event:
        rsvp?: "You cannot RSVP to this event"
        rsvp_opened?: "RSVP has been closed for this event"
        seats_available?: "This event is sold out"
```

To access the localized messages, you use the `#full_messages` method on the result object:

```ruby
policy.result.reasons.full_messages #=> ["RSVP has been closed for this event"]

# to access the top-level (rule) message
policy.result.message #=> "You cannot RSVP to this event"
```

Now go back to GraphQL and enhance our `expose_authorization_rules` macro to define a field with an `AuthorizationResult` type:

![](https://thepracticaldev.s3.amazonaws.com/i/9x5317vavmbuwv77077u.png)

Which contains a `reasons` field of a `FailureReasons` type:

![](https://thepracticaldev.s3.amazonaws.com/i/fdyjh0zica8h9ynxtzgs.png)

From the client perspective, it looks like this:

```graphql
query {
  event(id: $id) {
    canDestroy {
      value # true | false
      message # top-level message
      reasons {
        details # JSON-encoded failure details
        fullMessages # human-readable messages
      }
    }
  }
}
```

What are the pros of this approach?

- You have a standardized API for fetching the authorization information
- You have a single abstraction layer for dealing with authorization (policy classes)
- Adding authorization rules to the schema is simple (and, thanks to Action Policy [testability](https://actionpolicy.evilmartians.io/#/testing), testing is simple, too).

And all that functionality you get for free when using [`action_policy-graphql` gem](https://github.com/palkan/action_policy-graphql)!

---

Read more dev articles on [https://evilmartians.com/chronicles](https://evilmartians.com/chronicles)!