GraphQL-Shield (deprecated): How to *properly* implement Node server permissions

Jeremy Tong
4 min readJul 25, 2023

Note: I wrote this a year ago and neglected to publish. Since then, the plugin has been deprecated, but patterns described should still be relevant.

Intro

Recently, I was tasked by a client to help apply a permissions layer to an existing GraphQL node server. While they had basic authenticated routes, they wanted further role-based access control (RBAC) on CRUD operations throughout their app.

The pattern they had been using was to apply a permissions layer to their services, something we’ve commonly encountered. Though it makes intuitive sense to apply permissions to functions that interface with an ORM, it is an anti-pattern that raises a number of issues, discussed below.

It can be a daunting task without having an architectural pattern that clarifies the rule-based logic. graphql-shield is an awesome library that implements a useful permissions implementation pattern for Node GraphQL servers. It provides middleware to abstract the permissions layer away from other app infrastructure, following the conventional programming theory of separating concerns.

Why shouldn’t we use service level permissions?

Imagine you’re the head of an airport’s security.

Suppose you allow patrons to board any flight of their choosing, and only once aboard check their ID, luggage, boarding pass, and whether they were a pilot or passenger. This is like applying permissions to a service layer or ORM layer in a server app. But what’s the cost?

  1. All passengers would have to carry their identification and luggage to their seat and couldn’t stow it away or check it in until takeoff.
    A server would need to keep track of the user context throughout the state of the request, perhaps drilling a context parameter into every function.
  2. It would be difficult to consolidate the security process, resulting in TSA agents in every plane. How do you keep track of the overall state of security?
    A server would have permissions logic weaved into all other logic. One mistake by an unknowing developer and the entire application could crash.
  3. You could potentially confuse a TSA agent with a passenger, since there is a lot going on in the plane right before takeoff.
    A server would have provide permissions to grant itself access to functions, for instance, with a web-hook.
After each passenger is seated, airport staff must make new preparations. But the head of security (developer) forgot that the passenger isn’t airport staff. So only airport staff can take seats on flights?!?

Permissions should live on API-level

Like an airport security checkpoint, the permissions layer of a server app should live at a higher level of abstraction in the app. Identify the intention and the authorization of a user before allowing them to proceed, even though it might be easier to implement something at the time of the action.

In other words, permissions should generally live at the API layer when a user makes a request. In the case of a GraphQL Node server, permissions live on resolvers. GraphQL resolvers exist not only for queries and mutations, but other schema objects like return types, so we can fine-tune permissions to suit our security concerns.

In special high-security case, you can also apply database-level permissions. This would be in addition to an API-level permissions layer, not a replacement.

GraphQL-Shield

With graphql-shield, the permissions logic is simple to implement and understand.

  1. First, make sure your context contains all the RBAC information required for authorization.
  2. Next, define a set of rules that you might apply to different user requests. It is helpful to start with Query and Mutations, since those are the highest-level endpoints available to users.

If you need fine-grained control over what fields a query might return for a particular user context, you can even apply rules to specific fields. In the example below, the getFlightInfo() query allows any user to query a Flight object, but only airport staff and pilots can query the field airplaneDiagnostics.

Integration with an Auth Provider

In this case the client was using Auth0 to manage user authentication within their app. We often choose Auth0 as an authentication provider at Knit.

In order to integrate an auth library, inject the valid auth information via a request middleware and then add the injected object into the context. With Auth0, you can use the express-jwt library to simplify this.

Conclusion

At Knit, the security of our applications has always been one of our top priorities. Naturally, this is because we often build on behalf of other startups, so security is paramount in providing robustness to code audits.

For most startups, however, security doesn’t provide any tangible benefit to users. Hence, we’ve seen more than a handful of incomplete to bad implementations throughout our time working with startups.

With security, it’s important to note that if it kind of works, it doesn’t. The nice thing is, it’s not difficult to transition to kind of working to fully working. With tools like graphql-shield, you have the power to abstract permissions to the middleware layer, making this an easy and necessary win for all server applications.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Jeremy Tong
Jeremy Tong

Written by Jeremy Tong

Startup-Focused Software Developer

No responses yet

Write a response