7 Tips For GraphQL Security

7 Tips For GraphQL Security

Posted in

In December 2021, the security firm Salt Labs discovered a vulnerability in the GraphQL API that resulted in a data breach in a large B2B FinTech enterprise. Hackers were able to manipulate API calls to GraphQL to expose sensitive user data.

This particular company has since addressed its issue, but it raises a serious question. According to the 2022 State of GraphQL Report, 47.9% of developers are currently using GraphQL. That number is still growing, too. This means it’s of the utmost importance that developers and API producers do everything possible to ensure their GraphQL APIs are secure.

With that in mind, we’ve put together some best practices for GraphQL security to help you and your development team ensure your GraphQL security is as robust as possible.

7 Tips For GraphQL Security

1. Avoid Authentication Errors

Authentication errors are a common cause of GraphQL security breaches. In GraphQL, the user has to define the authorization flow. To further complicate matters, GraphQL has numerous authorization checks at both the query level as well as any that return data.

When authorization is handled solely by the query level, any unmonitored API becomes a possible attack surface. This means that the more complex a GraphQL API becomes, the more vulnerable it is to cyberattack.

2. Avoid Introspection

GraphQL is introspective, which means you can query a GraphQL schema to reveal extensive information on its data structure. When queried, GraphQL will return data about:

  • arguments
  • fields
  • types
  • descriptions
  • deprecated status

Unfortunately, this information can give attackers ideas for further attack surfaces. GraphQL also has a dedicated Integrated Development Environment (IDE) called GraphiQL. GraphiQL makes it even easier to expose potential attack surfaces thanks to its user-friendly, point-and-click interface.

Introspection is useful when a GraphQL API is still in development, but it’s a better idea to avoid it when your API goes live. It’s tempting to use introspection to teach users how to query your API, but it’s a better GraphQL best practice to use external documentation instead. Many implementations of GraphQL enable introspection by default, so engineers will have to disable introspection system-wide.

3. Disable Field Suggestions

If introspection is disabled, attackers may attempt to brute force a GraphQL API using a field commonly referred to as “field suggestion.” Field suggestion happens when an incorrect field name is supplied to a query. This results in an error response that returns suggestions of similar names.

For example, if you were to query for name when there’s no name field could result in something like the following being returned:

{
  "errors": [
    {
      "message": "Cannot query field \"name\" on type \"Query\". Did you mean \"node\"?",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ]
    }
  ]
}

The field suggestion is an attractive feature for API developers transitioning to GraphQL. Even so, you might consider disabling it as it’s generally a GraphQL security best practice to avoid any resource that could accidentally expose sensitive user data.

Don’t miss our workshop with Apollo GraphQL: What If All Your Data Was Accessible in One Place

4. Turn Off Debug Mode

GraphQL’s debug mode is mainly meant to return detailed error messages to facilitate the development process. But these errors may reveal information that could compromise GraphQL security. Debug mode can also cause issues when a GraphQL app is in production mode.

Instead, an alternative is to implement some sort of middleware solution that is only available to developers. Middleware also allows you to implement error masking or put a redaction solution into place.

5. Fine-Tune GraphQL Batching

One of GraphQL’s most popular features is query batching, which allows numerous requests to be bundled into a single query. Without putting additional parameters into place, however, query batching can result in GraphQL security vulnerabilities.

Consider this code snippet for requesting a user ID.

query {
  user(id: "101") {
    name
  }
  second:user(id: "102") {
    name
  }
  third:user(id: "103") {
    name
  }
}

This could easily be modified to let attackers query every possible user entry using enumeration. To make matters even more complicated, it can be challenging to monitor for suspicious activity as it would appear as only one query.

As a security best practice, it’s a good idea to implement some sort of rate limiting. Another alternative would be to exclude certain objects from batching, having them only accessible via REST instead.

6. Implement Input Validation Early

GraphQL queries just bundle API requests from other API architectures. This can lead to vulnerable data being exposed in some surprising ways.

For example, consider an API call requesting the inventory count of an itemID of a particular color.

inventoryCount.sh 200 red

An attacker could query for arbitrary variables, though, which could end up exposing vulnerable data. An attacker could query a GraphQL API with the following:

query {
    inventoryCount(itemId:80, color:”red; env ;”,)
}

Which could return something like:

As a general rule, you should implement input validation as early in the data flow as possible. All incoming data should be verified using the GraphQL enum and scalar data types.

You can also create GraphQL validators for more complex validation flows. You can use query allowlisting to create a list of pre-approved queries and then deny any query that’s not on the list.

7. Implement Rate Limiting

We already touched on this a bit when talking about GraphQL query batching, but it should be expanded upon. Rate limiting and time-outs are both excellent ways to counteract some of GraphQL’s more extreme security risks. GraphQL rate limiting is also not as straightforward as it is in REST.

Rather than monitoring API calls alone, GraphQL rate limiting involves tracking time, user ID, IP addresses, or other unique identifiers. Tracking these metrics requires implementing some sort of database solution like Redis. Redis is a good choice for rate limiting applications as it’s fast, lightweight, and supports key pairs.

You’ll also need to install some sort of library like graphql-rate-limit or ioredis.

An example of a barebones GraphQL rate limiter might look like:

const rateLimitOptions = {
  identifyContext: (ctx) => ctx?.request?.ipAddress || ctx?.id,
  formatError: ({ fieldName }) => 
    `Woah there, you are doing way too much ${fieldName}`,
  store: new RedisStore(redisClient)
}

const rateLimitDirective = createRateLimitDirective(rateLimitOptions)

This creates a function called identityContext that returns a unique string for every device or user in a database or else assigns their IP address as a fallback.

GraphQL queries are bundled together into one call, so monitoring solutions can sometimes overlook improper usage. There is a built-in rate limit of 5,000 points per hour, but a lot of sensitive data can be exposed before that alarm is triggered.

Final Thoughts on GraphQL Security

GraphQL has a lot of great features going for it. For instance, it can serve as a useful abstraction layer between users and your data. It also makes it nearly instantaneous to implement front-end clients for a wide range of environments. It even handles certain more advanced API functions, like deploying a backend or cache management, for you. Most importantly, GraphQL means that virtually any developer can interact with any API with no experience required.

GraphQL’s got its issues, though, primarily related to security. Following these GraphQL security best practices will help you avoid these shortcomings so that you can focus on the many benefits GraphQL has to offer.