GraphQL authorization with SPQR and custom annotations

Marcos Abel
Jun 3 · 5 min read
Photo by on

Here at , we have been using 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 because we feel it fits better in our workflow: it’s easy to use and configure, based on annotations and plays nicely with .

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.

Authorization vs Authentication

Similar words, different concerns. It is a classic question , 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 . 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 s 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.

The anatomy of a GraphQL response

Every is a map, containing a set of keys described in the . 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.

Sending GraphQL errors to the client

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 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 (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 that can be implemented by our exceptions:

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

This class ends up creating a which uses the information contained in your 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 magic, it opens up a world of error management possibilities.

Custom annotation for authorization

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 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.

AOP for authorization

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.

Wrapping up

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. We use Java, Rails, and JavaScript. This is where we write about the technologies we use at Trabe.

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.