A Guide to GraphQL Rate Limiting & Security

The Problem

Rate limiting and securing server resources are central problems when developing any kind of API. We want to prevent clients from being able to affect the experience of other clients, or simply avoid being taken down. That can happen, among other things, due to an excessive volume of requests, requests that are too expensive, or too many requests at the same time.

Rate Limiting

Let’s start with the problem of rate limiting. We want to make sure our clients have a certain quota of GraphQL requests to respect. However, we can’t simply count the number of queries. A ton of small queries is ok, while larger queries should be less frequent. How do we do this?

What to Rate Limit?

The most popular approach for GraphQL rate limiting is to assign a GraphQL document a “cost” or “complexity” and to use this number to rate limit instead of just counting the number of requests hitting our server. This is usually done in a static way, by analyzing the query AST before even executing it. However it’s possible to analyze it both during execution, or looking at the final response. The static analysis is important if you want to block queries above a certain complexity, before they execute. You could however, decide to rate limit through things like response or execution analysis.

  • To compute a cost, these algorithms usually depend heavily on pagination arguments. The same field, depending on if it returns the first 5 or the first 1000 items, can be vastly different in cost. Make sure you have mostly paginated list types, and that they are paginated in a hopefully pretty standardized way.
  • Pagination arguments are only an upper bound. Maybe the client is asking for 1000 items, but if there was only 1 item in the collection, we charged them 1000 points for nothing. Some folks have played with the idea of “refunds” if the response ends up containing less than what we charged up front, but this gets complex for clients.
  • There is no good way to determine how expensive a plain list type field will be. Generally have to be careful with those.
  • You may want to give mutations a bigger cost by default, in most applications, writes are more expensive than reads by default.

Algorithms

The rate-limiting algorithms don’t change much whether you’re using a GraphQL or REST API. Here are some common ones:

Dimensions

In some cases, simply rate limiting for cost per x amount of time, even if using something like a leaky bucket algorithm, might not be enough. Concurrency is a great example. Maybe we’re fine with 1000 complexity per second, but not fine with 100 times 10 complexity at the exact same time. You’ll want to think about some of those different dimensions. For HTTP APIs, sometimes we want to rate limit by:

  • Max request concurrency
  • Max requests a single resources
  • Any other domain-specific dimension you may have

Detecting and Blocking Malicious Queries

While we’re now able to give a general cost to incoming requests, and rate limit per client. However, there are approaches to crafting malicious queries we need to be aware of, some can be mitigated by query complexity, others might require specific handling.

Query Depth

There’s a lot of focus on abusing query depth in the GraphQL community. You’ll see that it’s only one way to craft potentially malicious queries, but it’s worth mentioning anyway since it's a popular concern.

query {
store {
product {
store {
product {
... and so on
}
}
}
}
}
query {
a: product { name }
b: product { name }
c: product { name }
d: product { name }
... and so on
}
  • Using query complexity, as we covered above. We want to use query complexity to block certain queries before they’re executed here, on top of using it as a rate-limiting mechanism
  • A lot of GraphQL libraries have a max query depth setting which you can use to limit depth. It’s great, just do not mistake this as a way to stop all expensive queries. It’s only one pattern of access.

Attacking the GraphQL Parser

Some smart attackers can slide in before we even had a chance to compute query complexity, during the parsing phase. This can be done in many ways, for example passing a list of billions of fields or arguments. GraphQL libraries often implement things to make sure you’re protected, but here are the two things to think about:

  • A max byte size for query documents. You probably have one already along the request path, but it is worth checking if it is right for your GraphQL API.

Attacking the Response Generation

Another way we’ve seen to attack a GraphQL API is to craft a query that results in a giant response, most commonly through generating validation errors. In general two things could help in those cases:

  • Having limits to input and field arguments, start with something fairly strict and be more open when needed (easier to relax an API than to make it more strict)

Observability

A quick note of observability. Make sure that if for some reason there was a resource exhaustion problem, you’re able to pinpoint which queries and which clients are the cause. Make sure you can answer some of these questions:

  • Which queries had the highest query complexity?
  • Which clients were sending these queries?

Query Allow Listing

Especially for internal APIs, It’s worth mentioning persisted queries and query allow-listing, which helps prevent intentionally malicious queries from getting executed by your GraphQL server.

The Multi-Layered Approach

Just like with certain hum… viruses, a multi-layered approach is usually best. Here are my recommendations:

  • Set a max byte-size to incoming GraphQL requests and make sure you or your GraphQL library implements protections for the parser.
  • Use pagination limits, and sane input limits on all potentially problematic fields.
  • Implement query timeouts. In case we missed something, we don’t want a malicious query to go on for minutes.
  • If something does go wrong, make sure you can identify which clients, IPs, and queries are the cause, and disable their access if you can.
  • Use persisted queries and allow listing, and make sure to run some of these safe guards at query registration time.

--

--

#GraphQL Enthusiast, Speaker, Senior Software Developer @ Netflix 📖 Book is now available https://book.productionreadygraphql.com/

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