Authentication and Authorization in GraphQL with gqlgen

Introduction

Recently, we launched Skim, a platform inspired by arXiv Sanity Preserver, to help researchers conveniently access technical papers, give them capabilities to organize these papers in racks (think of them as Spotify playlists, where instead of songs you would store papers in them), and to provide better visibility of details related to Conferences. When we first started building Skim, new features were being shipped every alternate day, which had a lot of changes impacting both, our REST APIs designed with Node.js as well as our React Client. With so many developments happening, it was imperative to devise a backend server solution which was easy to manage and ship, causing minimal impact to the User Interface. We soon realized that a GraphQL service written in Golang, would be better-suited for this platform and would also drastically boost it’s overall performance. We wanted to make this switch happen in a very short span of time and with a little research we were convinced that gqlgen would be an ideal choice to get our GraphQL server up and running in no time!

Skim Architecture

Prerequisites

This post assumes that the reader understands GraphQL schemas, resolvers, fields and operations like mutations and queries.

Problem

While gqlgen definitely enabled this entire migration to happen smoothly, there was one problem that got us scratching our heads. If one would look at gqlgen's documentation, they would find well-written recipes for some of the most common use cases including Authentication Middleware. But the catch here was - this middleware would introduce auth to all the queries and mutations present in our schema. Skim intended to provide some of these queries and mutations to the users without needing a user to sign up for this platform. How could we achieve Authentication and Authorization in gqlgen and at the same time make selective queries and mutations available to all users who wouldn’t have the necessary HTTP cookies to access the service?

Solution

After hours of going through GraphQL servers implemented with gqlgen, we stumbled upon the idea of using Schema Directives. But before we get into the actual implementation, here’s some glossary.

Authentication

Authentication is the act of ascertaining that users are who they claim to be. The most common forms of authentication are through usernames & passwords, biometrics or One Time Pins (OTPs). Here's a simple explain the authentication logic.

Authorization

Authorization entails rules to determine who is allowed to do what. It is the process of giving users the permission or rights to access resources within the system. The most common Authorization mechanisms are using JWT tokens or API keys.

getStoredUserDetails has been left for the readers to implement.

The Auth Middleware

Now that we've understood both Authentication and Authorization, let's put these two together to create our Auth Middleware.

Yes, I know there's a lot going on here, so let me explain it to you. As soon as the flow of control reaches the middleware, we first retrieve the Auth key (in this case it's a JWT). At this point, if we find the token missing or encounter any error, we simply do not execute the next steps and let our Schema Directives take care of the permissions. If all goes well, we proceed with authorizing the token we just extracted. If the users are authorized, we will decode the JWT to get their details which were added as custom claims. These details are then used to check if the users trying to request the service are authentic users. If they are, we set their details in context, and then proceed with the request, but now, with our new, updated context.

At the time of initializing the server, we should now wrap it within our newly written Auth middleware.

Schema Directives

A Directive is the simplest way of providing options to alter the runtime execution behavior in a GraphQL document. It is an identifier followed by Directives can also be used to describe additional information for types, fields, fragments and operations.

In the above example, we've used the GraphQL spec's reserved directive @deprecated. Simply put, it signifies that liked field is now deprecated and user should instead request for the followed attribute.

Schema Directives, on the other hand, are directives that appear in the GraphQL schemas written using the Schema Definition Language (SDL). gqlgen offers developers the capability to write their custom schema directives out-of-the-box which is ideal for our use case of building an auth middleware specific to our operations. This brings us to the final step of being able to selectively enable our Auth middleware for our GraphQL operations.😌

Firstly, we need to tell our server that it needs to take into consideration, the custom directive defined by us. We do so by assigning the our directive to the Config.

Next, we update the schema.graphql file to declare our custom directive.

Here, we're defining a query and a mutation. Our query is simply supposed to retrieve a Rack's attributes when the user provides a valid Rack ID. Of course, we don't intend to restrict this operation to authorized users only, and hence, we do not specify our schema directive @isAuthorized against this operation. Next, we have a mutation that allows a user to create a rack and as expected, we want to ensure that only authorized users are able to do so. Hence, we add the @isAuthorized against this operation.

It's that simple! Now we can build our entire system to cater to even those users who don't want to sign up to use our platform (although, if you're a research enthusiast, I would highly recommend you do.😬)

While this marks the end of my long post explaining the GraphQL Auth middleware we implemented for Skim, I would love to hear from you, if you have another solution. You could reach me on any of the social links mentioned above. Until next time!

References

・gqlgen
・Directives - Apollo Docs
・Making and Using HTTP Middleware
・GraphQL directives are underrated