How to Secure Your GraphQL API From Malicious Queries

Security and efficiency for your GraphQl API

Sachin Thakur
Better Programming

--

Photo by Clint Patterson on Unsplash

With all the libraries and online communities around these days, building graphql APIs is easy. Still, there are probably some questions in your mind. Like, how do we actually secure our server? And how do we restrict or whitelist certain queries to run on our server?

If you’ve ever used graphql you may be aware of the graphql query loop. Let’s take a look at an example:

Do you see any issues with the above query type? We can have an infinite loop here and if someone runs this query against our server it could definitely crash our server or create something like a DOS attack. If a malicious user can create a nested query that hurts your back end, that’s a problem. There are many approaches to solve this problem — let’s look at a few of them.

Size Limiting

One naive approach would be to limit the size of the query by raw bytes since in graphql all requests are treated as a post request and all queries are a part of the body as stringified objects. However, this might not work in all cases and could end up hurting you instead as some of your valid queries with long field names might end up failing.

You can run the code above before each request inside a middleware. It will run for each request that’s coming into your graphql server, validate them all, and reject any query which is too long.

Depth Limiting

Another approach would be to limit the nesting only to an nth level. You can define to what level you allow execution of the query and strip the rest of the fields after the nth level. One good package to do this is graphql-depth-limit which limits us to define the depth of the query we want to allow on our server. graphql-depth-limit works really well with both Express server and Koa. Even if you’re using the Apollo server it can work really well with that too.

Query Cost Analysis

In depth limit, we’re limiting the execution of queries to nth level but it might not be suitable for all cases. Sometimes the depth can be a lot less but the cost to compute that query can be high. This might happen when we’re fetching a lot of data in a single query and it’s putting a lot of load on our back end server or database server. These queries might look something like this:

Even though this query is only two levels deep, you can understand the level of complexity — the amount of data it will be requesting from the database server and computation happening on the backend server.

This issue will not be resolved by either depth limiting or size limiting, so we need something robust that can handle this kind of query. Often in these cases, we need to perform a query cost analysis, where our server computes the cost of each query and decides whether to allow this query or reject it.

To do this, we need to analyze each query before running them on our server. If they’re too complex or too expensive, we need to block them. There are numerous open-source libraries which have been built by some really smart people — one of those libraries is graphql-validation-complexity. You can separately define complexity for each field, like different complexity for scalar types and objects. There is also graphql-query-complexity which calculates the complexity based on each field, unlike graphql-validation-complexity which calculates it based on the types. Adding query cost analysis using either of these libraries is pretty straightforward:

Before you start implementing query cost analysis on your server, just make sure your server really needs it. Otherwise, it will just be another overhead for your server and you’ll just end up wasting resources and time. If your server doesn’t any complex relations fetching you might be better off without query cost analysis — just add size limiting and depth limiting.

Query Whitelisting

Query whitelisting is a little complicated and can be a double-edged sword sometimes. Let me explain it in simple terms.

Every restaurant has a name or number assigned to each dish they serve, so instead of saying the whole name of the dish like “cheese pizza with a double cheeseburger with olives and fries on the side” you just say “Number 2.” This saves you both time and effort. In this case, you’re just saving a few words but you are saving something. But when it comes to requests from your client to your server you can save a lot of request data if you don't send the entire query and instead just send the hash of the query.

These are known as “persistent queries” in graphql terms and they save you data on request and protect your graphql queries from malicious queries being executed on your server. Basically, you need to compile a list of all the allowed queries ahead of time and check any query against this list. You can even generate a hash for each query and just send the hash value in the request.

The request will look something like this:

No one can actually know the schema server is running, or which query or mutations are being run — it’s just a hash. If your queries are totally static and you’re not using a library like relay to generate these queries dynamically, this might be the most reliable approach for you. You can even automate the entire process of hashing the queries and putting it inside your production application. You won’t require the query validation on your server since you already know all the queries being run on the server.

Before you go and start implementing Query whitelisting just know a few limitations:

  • It will be really difficult for you to add, remove, or modify any query on your server since now you have to communicate with all your clients and give them new hashes. If anyone runs a query which has been modified slightly, it will result in a query failure.
  • If you are building Public APIs which are accessible by developers other than your own team, it’s really not a good idea to go with this approach.
  • Unexpected slight changes in your queries can cause your application to crash if there is ever poor communication between teams.

Conclusion

To summarize, I would recommend using Depth Limiting as probably something every GraphQL server should have by default. After that, you can build on top of it, adding more layers to make your server more secure.

I feel that query whitelisting is for a specific type of application. You should properly analyze your needs before implementing it.

Some other, not so talked-about approaches would be implementing a query time-out, so your queries don’t run infinitely and crash the server. Query cost analysis is a little complicated but it protects your server the most against malicious queries.

--

--