Protecting Your GraphQL API From Security Vulnerabilities

Tom Nagle
Tom Nagle
Dec 5, 2019 · 4 min read

GraphQL is quickly becoming the tool of choice for developers that need to build an API for their client application. But like all new technologies, GraphQL comes with its own threat landscape. Whether you’re building a side project or a large-scale enterprise application, you’ll need to make sure you’re protecting yourself against these GraphQL security vulnerabilities.

While the threats listed in this post are specific to GraphQL, your implementation will introduce a new set of threats that have to be addressed. It’s also important that you understand the threats that put every application exposed to the internet at risk.

Threat: Large, deeply nested queries that are expensive to compute

Solution: Depth limiting

The power that GraphQL provides comes with some new security threats. The most common is deeply nested queries that result in expensive computations and large JSON payloads that can disrupt your network quality, or take it down altogether.

The right way to protect your API from this kind of attack is to limit query depth so maliciously deep queries are blocked before the result is computed.

GraphQL Depth Limit provides an easy interface for limiting the depth of all queries.

import depthLimit from 'graphql-depth-limit'
import express from 'express'
import graphqlHTTP from 'express-graphql'
import schema from './schema'

const app = express()

app.use('/graphql', graphqlHTTP((req, res) => ({
schema,
validationRules: [ depthLimit(10) ]
})))

Threat: Brute forcing vulnerable mutations

Solution: Rate limiting

Brute forcing login forms is the oldest trick in the hacking book. In the past decade, the internet has experienced so many large data breaches that a pack of 772,904,991 unique emails and 21,222,975 unique passwords was recently exposed and reported by Troy Hunt of Have I been Pwned.

Breaches like this put every website and application exposed to the internet at risk. Attackers can hit your login mutation with emails and passwords that appear in these lists until they get the response that they’re looking for.

Fortunately, there is an easy way for you to make this really difficult and slow for attackers, making you a less appealing target.

This GraphQL Rate Limit plugin allows you to specify limits on your queries and mutations in three different ways, custom directives graphql-shield or with the base rate limiter function.

The plugin allows you to set the time window and a limit. Setting a large time window on highly vulnerable mutations and queries, like login and shorter limits on less vulnerable queries will help you maintain a nice experience for legitimate users and a nightmare for attackers.

Create a rate limit directive:

This is a unique identifier for each request. You can use the user’s IP address or another identifier that is unique to that user and consistent for every request.

Add the directive to your schema:

Finally, add the directive to your vulnerable mutation:


Threat: Allowing user input where you should infer it

Solution: Infer input from the user’s session where you can

It’s easy to think that if you want to allow a user to update a resource then you should let them specify what resource they want to update. But what if they get the ID of a resource that they don’t have CRUD privileges on?

Let’s say that we have an UpdateUser mutation that allows a user to update their profile.

Without any server-side protection, an attacker with a list of IDs would be able to update the email address for potentially any user. The obvious solution here is to add a test to make sure the current user’s ID matches the ID in the input fields.

Don’t do:

The less obvious but correct way to fix this problem is to not allow ID as an input and use the user’s ID from the context object.

Do:

While this may be a trivial example, doing this for every asset that has a one to one relationship with your user object can protect yourself against a lot of risky mistakes.


Threat: Running multiple, expensive queries simultaneously

Solution: Query cost limitations

By assigning a cost to each query and specifying a maximum cost per query, we can protect ourselves against attackers executing multiple expensive queries simultaneously.

The GraphQL Cost Analysis plugin is an easy way to specify costs and cost limits.

Specify your maximum cost:

Specify a cost for each query:

Threat: Exposing GraphQL implementation details

Solution: Disable introspection in staging and production

The GraphQL playground is an extremely helpful tool for development. It’s so powerful that it will even document your schema, queries, and subscriptions for you. This information could be a gold mine for attackers looking to find exploits in your application.

The GraphQL Display Introspection plugin will prevent your schema from being leaked in publicly facing environments. Simply import the plugin and apply it to your validation rules.

The Startup

Medium's largest active publication, followed by +564K people. Follow to join our community.

Tom Nagle

Written by

Tom Nagle

I am a full stack JavaScript developer, living in Melbourne, Australia. My preferred stack is Mongoose, TypeScript, Node.js, React & GraphQL.

The Startup

Medium's largest active publication, followed by +564K people. Follow to join our community.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade