How to limit GraphQL queries for better security and performance
--
GraphQL is a magnificent technology that allows a lot of flexibility in terms of data fetched by the client, one dare even say too much flexibility. Indeed, a client can make excessive and abusive queries which can be source of security and perfomance issues.
There are different strategies to mitigate these concerns, like applying a query depth limit which will automatically reject queries if their nested fields go beyond a certain depth. However, this depth limiting approach is too generic, and doesn’t take into account domain-specific queries that can be expensive without using extensive depth. Another solution is query whitelisting or persisted queries, it can certainly make your GraphQL API more secure as only predefined queries from the clients will be accepted. But this approach, while being needlessly complex to implement for any non trivial schema, when applied rigorously, sacrifices the very idea of flexibility which is at the core of GraphQL. There is also the cost analysis strategy, it defines a query complexity limit that must be higher than the sum of predefined arbitrary numbers assigned to each object returned. That’s a great idea… as long as it stays an abstract idea without the need of actually implementing it. Again, it’s needlessly complex to implement.
A good strategy consisting of filtering GraphQL operations based on the shape of the requested fields should meet, at least according to me, the following criteria:
- Easy and simple to implement
- No extra coding on client side
- Flexibility of GraphQL maintained, at least to some extent
When I looked for such strategy and didn’t find it anywhere… well, I created it and published a node module called graphql-secure-data. But it’s nothing fancy really, just a couple hundred lines of code, a middleware that can be integrated with graphql-middleware module. I figured I could leverage the info object in the GraphQL resolvers in order to filter the incoming operations.
I invite you to delve into how this module is used.
Installation
yarn add graphql-secure-data graphql-middleware
Example
We will be using GraphQL Yoga for our example, and notice we only need one import from graphql-secure-data:
Let’s say we have the following schema type definitions:
And we would like to prevent queries such as these:
The nested fields inside that red rectangle are normally not supposed to be accessed by the user. By the way, I would like to point out here the limitations of applying field authorization checks, you could apply them to password and content, but what about id and username fields…
Here is how you can restrict your returned GraphQL data types to certain fields and subfields:
So, the use of secure function is pretty straight forward: you enter the restricted data types at the top of the object map, and you enter for each type the fields and subfields that you want to allow, going as deep as you’d like. Fields that you omit will not be allowed to be fetched, an error will be thrown if any query, mutation or subscription tries to fetch them. However, types that you omit at the top of the object will be fully allowed without any restriction from this middleware, this way we can leave certain returned types free of any restrictions from this module.
The secure function returns a middleware, and thanks to graphql-middleware you can apply it to your schema. In case of an excessive query, a few lines of code will save you from having the query being actually resolved.
Here is what our GraphQL server would return in case of the initial excessive query above:
And the same error would be returned if it was a mutation or a subscription that was requesting access to the same data.
Wildcards
In our restricions above I used a wildcard (“*”) to avoid having to name all the allowed non-nested fields. There are actually two different use cases for wildcards:
- Wildcard at a given depth allows all direct non-nested subfields to be fetched without having to name them all. It doesn’t include nested subfields (same as we used above)
- Wildcard(s) at the base of a type to specify the depth limit up to which all fields and nested fields are allowed for a given type. Example:
The latter basically allows custom depth limit per type, which can be very useful.
Conclusion
Limiting GraphQL queries as a solution to increase security and performance of your GraphQL API can only be worth it if the strategy includes ease of use and maintaining a delicate balance between flexibility and safety. And this simple idea inspired me to create the module graphql-secure-data which is an easy to use middleware allowing your GraphQL server to filter queries based on the shape of the requested data while maintaining a reasonable and safe level of flexibility of GraphQL.
Related links:
If you found this article interesting, leave a like and follow me for more future tips on development with GraphQL, NodeJS, React and more! If you have any question or a feedback, you’re most welcome to leave a comment.