GraphQL authorization with SPQR and custom annotations

Marcos Abel
Jun 3, 2019 · 5 min read
Photo by Michael Dziedzic on Unsplash

Here at Trabe, we have been using GraphQL for a while. We started our dive in this technology with pure JavaScript projects but, after realizing how well GraphQL and React play together, we decided to adopt it for all new projects involving a React UI.

Since a good number of our projects are based on a server-side Java stack, we spent some time benchmarking the available GraphQL libraries for the Java ecosystem. We finally decided to go with GraphQL SPQR because we feel it fits better in our workflow: it’s easy to use and configure, based on annotations and plays nicely with Spring Boot.

GraphQL SPQR (SPQR from now on) provides us with a set of annotations to easily generate our schema, but, as a project in its early life stages, it doesn’t provide support for all the authorization and authentication needs that we can possibly have.

Similar words, different concerns. It is a classic question answered plenty of times all around the internet, but still, one that needs a short reminder to avoid misconceptions:

Authentication is the process of ascertaining that somebody really is who they claim to be.

Authorization refers to rules that determine who is allowed to do what. E.g. Adam may be authorized to create and delete databases, while Usama is only authorised to read.

In our scenario authentication is handled using Spring Security. At the end of the day, the Spring Boot starter for SPQR creates a regular controller so we can use the regular Spring Security stuff to manage authentication.

We could also think about using spring security support for authorization but it just doesn’t fulfill our needs. We need to send extensive information about errors to our GraphQL clients and that cannot be done easily without using a custom solution.

Every GraphQL response is a map, containing a set of keys described in the specification. When an error happens, the map should include a key errors containing the list of errors that happened when trying to fulfill the request. The keys that can be present in an error are also defined in the specification.

A typical error looks like this:

The GraphQL specification provides us with a mechanism to include additional information: we can add theextensions key and include a map of custom values in our errors.

Now we know that the specification allows servers to add custom fields to GraphQL errors. We just need to know how to do that using SPQR.

SPQR uses GraphQL Java under the hood. SPQR generates the schema for us, allowing us to use a code first approach and to develop GraphQL APIs rapidly, but the execution of the queries and mutations is handled directly by GraphQL Java. As a result, error handling is also performed by GraphQL Java.

When you throw an exception in a resolver method, that exception will be handled by the ExecutionStrategy (part of GraphQL Java). You can fully customize your ExecutionStrategy to do whatever needs to be done with your exceptions, but the authors of GraphQL Java have done a pretty good job with the default implementations and have provided support for providing custom errors in an easy way.

GraphQL Java provides an interface GraphQLError that can be implemented by our exceptions:

All the default implementations of ExecutionStrategy(synchronous, asynchronous and batched) use SimpleDataFetcherExceptionHandler to handle the exceptions.

This class ends up creating a ExceptionWhileDataFetching which uses the information contained in your GraphQLError to compose the error response:

This behavior allows us to generate custom, well-formed GraphQL errors just throwing certain exceptions. This doesn’t seem like that big of a deal but, combined with some AOP magic, it opens up a world of error management possibilities.

Let’s start with an example of a typical SPQR resolver:

As you can see, we include a context object in our resolver. We assume that we have a ServletContextFactory in our project creating that context object for every request.

This context object contains information about the current user of the application (for example the context object could be created using the information contained in a JWT token).

We need to define a custom annotation to be used whenever we need our authorization logic to be executed:

And annotate our example resolver:

Our resolver is done. The only missing piece in the puzzle is the aspect itself.

Authorization is a complicated topic. The authorization logic can be diverse depending on the scenario. Let’s assume for this story that in our scenario we need to check the value of some field of the object retrieved using the service method.

For this scenario, we can perform the authorization using an Around advice:

Let’s take a look at the key points of this aspect:

  • We first define the Pointcuts needed for our aspect.
  • Then we use those pointcuts to define an Around advice that will fire for every public method annotated with @CheckPrivileges and @GraphQLQuery or @GraphQLMutation.
  • The authorization logic itself, in this case, is simple: we execute the advised method and then check the value of a given field.

Of course, we are still missing some pieces of the puzzle. In our example we use a custom exception:

This exception implements GraphQLError . This is the key point that allows us to customize the GraphQL error with our custom extensions.

As you can see, the constructor for the exception receives an ErrorInformation enum. This design allows us to use the same exception for different kinds of errors.

The implementation of the enum, in this case, will be:

With all the pieces in place, the error response generated by SimpleDataFetcherExceptionHandler will include two keys inside the extensions map:

  • a field with the error type.
  • a field with the key of the message that our UI is going to show to the user when the error happens.

We have developed an AOP based solution to generate custom GraphQL errors for SPQR resolvers. This solution is not limited to authorization and can be used for all kinds of error generation scenarios, allowing us to keep our code focused on the main concern and extract error generation to a collection of purpose-specific aspects.

Trabe

We are a development studio.

Thanks to David Barral, Asís García, and Oscar Arias

Marcos Abel

Written by

Co founder@ Trabe Soluciones

Trabe

Trabe

We are a development studio. We use Java, Rails, and JavaScript. This is where we write about the technologies we use at Trabe.

Marcos Abel

Written by

Co founder@ Trabe Soluciones

Trabe

Trabe

We are a development studio. We use Java, Rails, and JavaScript. This is where we write about the technologies we use at Trabe.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store