Directive permissions are a declarative way of implementing authorization rules in GraphQL servers. In this article, our guest author Dennis Walsh explains how to use them in order to protect your data.
If you're interested in writing an article for our blog as well, drop us an email.
GraphQL servers send your app into the world wearing only its birthday suit — everything is exposed. A quick introspection query reveals all possible API operations and data structures. That could change, but for now, all of your operations and data are laid bare.
Therefore, one might anticipate authentication and authorization are GraphQL first class citizens. But, neither of them are part of the official spec. That lack of direction created a lot of sleepless nights for my GraphQL server development, and it wasn’t until I watched Ryan Chenkie’s talk about directive permissions that I found a solution.
In this post, we will first talk through a naive approach to GraphQL permissions and find out about its drawbacks. Then, we’ll discover directive permissions and learn how they provide a declarative and reusable alternative.
To play around with directive permissions, check out this Launchpad demo.
Permissions in GraphQL: A Naive Approach
A Simple Example
When naively implemented, the code for permissions in your GraphQL server quickly becomes repetitive. Let’s start by quickly reviewing how most GraphQL servers implement permissions in a naive and simple fashion.
As an example project to anchor the discussion, here’s our GraphQL schema definition. The schema defines the API for car dealer software.
Several fields should scream “malicious user fun”. To protect the API, let’s make up a few permissions rules for it:
updateVehicleAskingPrice
should be restricted to managerscostBasis
should be restricted to managersnumberOfOffers
should be restricted to authenticated users
Naively Implementing Permissions in GraphQL Resolvers Creates Duplication and Mixes Concerns
Using Prisma in combination with Prisma bindings as a data access layer, here’s how the restriction for the updateVehicleAskingPrice
mutation can be implemented:
We’re using the exists
function on the Prisma
binding instance (line 4) to ensure the requesting user has proper access rights for this mutation. If this is not the case, the mutation is not performed and will fail with an “Invalid permissions”-error instead.
Field-level constraints get even more interesting. Here is how we can protect the numberOfOffers
and costBasis
fields on Vehicle
:
With this approach, our resolvers become cluttered with redundant permission logic. As a first improvement, we can write a *wrapping function *that abstracts away some of the redundant authorization logic:
That looks a lot cleaner already. However, there is no straightforward way to figure out which fields are protected. This will always require digging into the resolver implementations and actually read the code.
A Declarative Approach to GraphQL Permissions Based on GraphQL Directives
Embedding Permission Directives in the Schema Definition
Contrast the above schema and resolver implementation with this schema:
At a glance, we know what is protected and what is not. In my book, that’s a win! 🍻 How do we get to such magic? You guessed it — directive resolvers.
Realizing Directive Resolvers Into Permissions is Straightforward
You can think of directive resolvers as resolver middleware. An incoming request first hits the directive resolver. If it passes (i.e. next()
is invoked), the request continues to the actual field resolver. Here’s how we check for an authorized role inside a directive resolver:
Enforcing permission rules with GraphQL directives allows to remove any authorization logic from the field resolvers:
Note that setting up directiveResolvers with graphql-tools is straightforward:
Changing Directive Permissions Takes Little Work
Now, let’s change the requirements:
numberOfOffers
should be restricted to managers (formerly just authenticated)askingPrice
should be restricted to authenticated users (formerly unauthenticated)
Here, hold my beer…
…Done! We can update our permissions simply by adjusting the directives in the schema definition, without touching the actual implementation. The generic directive resolvers take care of enforcing the rules.
Demo: Experimenting With Directive Permissions
The best way to learn is experimentation, so I made a Launchpad demo to play around with permissions, queries, and users.
For instance, try running this mutation as different users:
Change the directives on the schema to see how the permissions change. Note that Launchpad’s GraphiQL shows errors first, scroll down to see data.
I also made a demo repo combining Prisma, Auth0, and directive permissions.
Potential Drawbacks & Alternatives
Some people argue against “polluting” the schema with information that goes beyond the actual GraphQL schema definition. I appreciate that argument. For a different approach that doesn’t touch your schema definition, check out GraphQL Shield by Matic Zavadlal. It even has a few ingenious tricks like caching permission functions per request.
Directive Permissions Provide Clear and Easy Authorization
Permissions in GraphQL can be difficult at first. With RESTful APIs, authorization is implemented by protecting the individual API endpoints. This approach does not work for GraphQL because there’s only a single endpoint exposed by the server.
Therefore, authorization in GraphQL requires a major shift in thinking. The official GraphQL spec doesn’t provide any guidance and best practices for implementing permissions are still emerging. One of them being *directive permissions *which we covered in this article.
To understand directive permissions, we first took at look at the cumbersome resolver implementation when going for a naive approach. We then learned about directive permissions and how they provide a declarative alternative by extending the GraphQL schema definition with dedicated GraphQL directives.
I believe this emerging directive permissions pattern finally gives GraphQL a clear and declarative path to securing data. It does so by separating permissions and data into their respective layers with a concise and expressive syntax.
To get some practical experience with the examples explained in this article, check out the live demo on Launchpad or the code in this GitHub repository.
I like blogging about new software patterns and intellectual property law. If we share those interests, please consider following me here and on Twitter @LawJolla.
Don’t miss the next post!
Sign up for the Prisma Newsletter