Generic Authorization with GraphQL and Ruby
This article was extracted from a Ruby talk given in the Amsterdam.rb meetup. You can see the first article here.
The GraphQL gem provides us with field instrumentation, which is a good way to execute code around your GraphQL fields. That link shows how to do a simple timer to check how much time it took to resolve the field. But what if we could extend this idea to do more helpful and complex executions? This article will show 2 instrumentations we are using to handle authorization in our queries.
Authorize a single element
Imagine that are 2 organizations. We are a member of organization 1 but not organization 2. To retrieve the number of members from each of them, we use a query like this:
{
my_org: organization(id: 1) {
members {
totalCount
}
}
other_org: organization(id: 2) {
members {
totalCount
}
}
}
The output of the query might look like this:
{
“data”: {
“my_org”: {
“members”: {
“totalCount”: 9
}
},
“other_org”: null
},
“errors”: [
{
“message”: “forbidden”,
“path”: [
“other_org”
]
}
]
}
Since we can’t access other_org
, the data for this organization is null
. In addition there’s an error message pointing to that organization (following the GraphQL specification) . Here’s how we do it using field instrumentation.
- Create your instrumentation class:
2. In the schema, add a metadata definition and instrument your fields:
3. And use it!
Now, let’s see what’s going on here: in the first step, we replace the resolve
proc of the field with a different one. This proc gets the result of the old definition, checks against a policy and raises an error if it’s not allowed. The policy_class
is a regular Pundit policy.
In the second step, we configure the fields to accept the access_permission
metadata, and add a line to the schema to configure the instrumentation. The metadata is the key piece here that allows us to make the code reusable.
In the third step, we have the line access_permission(policy_class: OrganizationsPolicy, action: :load?)
which describes the Pundit policy class and which action it should use.
And that’s it! To apply this to other fields, we only need to add that one line and we’re all set.
Scoping down a collection of elements
In the previous query, we asked the total number of members in the organization. There are 9
members in total, but only 2
are members of our team. What if we want to restrict that count to only include members that belong to our team?
To achieve this, we’ll use the same principle as the previous instrumentation, but with one difference in the authorization_proc
. It will use Pundit scopes to narrow down the query before returning a response.
- We’ll rewrite the
authorization_proc
to look like this:
2. The way to use it is the same, except that now we don’t need an action parameter.
Keep in mind that initial_scope
must be a ActiveRecord::(Relation|Connection|other)
(or Sequel::Dataset
in our case) class so when it’s passed to Pundit it can add the proper WHERE
clause that’ll filter the collection. A Scope class for our example would be:
Now the query returns only the 2 members of our team!
# Query{
my_org: organization(id: 1) {
members {
totalCount
}
}
}# Result{
“data”: {
“my_org”: {
“members”: {
“totalCount”: 2
}
}
}
}
Looking forward
We are currently exploring new ways to use features from the GraphQL Ruby gem to simplify repeated tasks in creating the graph. For example, a presence
metadata for adding “not found” errors.
Are you using some component in a way that simplifies your workflow? Leave a comment!