Preventing GraphQL batching attacks
Tags: graphql, javascript, webdev, node
Published: 2022-02-07 19:06:18


In this post, I'm going to explain what is GraphQL batching attack, and how you can defend from it.
GraphQL can send multiple queries with a single request, and this can be open to abuse. Consider this mutation where we are trying different password/username combinations.

```js
  mutation {
    login(pass: 1111, username: "ivan")
    second: login(pass: 2222, username: "ivan")
    third: login(pass: 3333, username: "ivan")
    fourth: login(pass: 4444, username: "ivan")
  }
```
This is effectively the same query (`login`) that is **aliased** to different names.

Or this query, which can easily DoS your server.
```js
  query {
    getUsers(first: 1000)
    second: getUsers(first: 2000)
    third: getUsers(first: 3000)
    fourth: getUsers(first: 4000)
  }
```

There are a couple of techniques that can be used to prevent this kind of problem one of them is [GraphQL Query Complexity Analysis ](https://github.com/slicknode/graphql-query-complexity) which is, as the name suggests, very complex to implement correctly. It requires analysis of how the graphql API is used, and what queries and mutations are most often called. If you get this wrong, there is a danger of the server denying perfectly valid queries.

The second solution that can somewhat eliminate this problem is implementing `grapql dataLoader`(https://github.com/graphql/dataloader) which is also tricky to get right, and it will require you to change your resolvers.

The third solution which I will present here is to simply disable duplicate queries and mutations.

### How it works

While the `alias` functionality cannot be directly disabled (at least not in the current Grahpql JS implementation), we need to analyze the query that is coming to the server and if it contains duplicate queries and mutations, simply deny the request.

To deny the request we need to hook in the `validation` phase of the GraphQL server. The validation phase is the phase when the request is received by the server, but before it is executed, at that point we can decide if we want to proceed with the execution of the request, or immediately return to the client with the reason why the request has been denied.

For this, we are going to use [GraphQL No Alias library](https://github.com/ivandotv/graphql-no-alias).

There are two ways to use this library:

- Using the `@noAlias`directive in the `schema`
- [Using the configuration options](#Imperative-configuration) (better performance)

### Using the directive

There are two parts, a `directive` that needs to be added to the `schema`, and a validation function that needs to be added to the GraphQL `validationRules` array.
In the next example, we are going to start implementing the `@noAlias` directive by limiting `all` mutations to only *one* of each (by specifying the directive directly on the mutation type), and we are going to limit query `hello` to maximum 2 calls in the same request. For the actual GraphQL server we are going to use `express-graphql` but the directive should work with any server implemented in javascript.
In the upcoming examples I'm going to use `express-graphql` as the graphql server, simply because it is easiest to setup, however useage with any other server is the same.
```js
const express = require('express')
const { graphqlHTTP } = require('express-graphql')
const { buildSchema } = require('graphql')

const {createValidation} = require('graphql-no-alias')

// get the validation function and type definition of the declaration
const { typeDefs, validation } = createValidation()

//add type defintion to schema
const schema = buildSchema(`
  ${typeDefs}
  type Query {
    hello: String @noAlias(allow:2)
  }

  type Mutation @noAlias {
    login(username:String, password:String):String
	getUsers(startingId:String):String
  }
`)

const app = express()
app.use(
  '/graphql',
  graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true,
    validationRules: [validation] //add the validation function
  })
)
app.listen(4000)
```
Now if you send a query like this:
```js
  query {
    hello
	hello_2: hello
  }
```
It will **pass** however, this query will not (because the maximum allowed calls for query `hello` is 2 calls)

```js
  query {
    hello
	hello_2: hello
	hello_3: hello
  }
```

And for the mutation:

```js
  mutation {
    login(pass: 1111, username: "ivan")
    second_login: login(pass: 2222, username: "ivan")
  }
```
This will fail because you can't have any duplicate mutations (`@noAlias` directive is set directly on the `Mutation` type, with no value, which means that the default value of 1 will be used.


And that's it, that is all it takes to manipulate the number of queries and mutations in GraphQL requests.

Next, we are going to look at using the `graphql-no-alias` validation imperatively.


### Imperative configuration

There is another way to use `graphql-no-alias` validation directive, and that is with the **imperative** configuration.

When using **imperative** configuration there is no need for type definition and schema modification, this also results in better performance since the `schema` is not analyzed (not looking for directives). All we need to do is to create a simple Javascript object with appropriate keys and pass that object to the `createValidation` function.

```ts
const permissions = {
  Query: {
    '*': 2, // default value for all queries
    getAnotherUser: 5 // custom value for specific query
  },
  Mutation: {
    '*': 1 //default value for all mutations
  }
}
const { validation } = createValidation({ permissions })

const schema = buildSchema(/* GraphQL */ `
  type Query {
    getUser: User
    getAnotherUser: User
  }
  type User {
    name: String
  }
`)

const app = express()
app.use(
  '/graphql',
  graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true,
    validationRules: [validation] //add the validation function
  })
)
```

That's it, that is all it takes to disable multiple identical queries and mutations to be sent in a single request to a GraphQL server.

Make sure to check out the [library on Github](https://github.com/ivandotv/graphql-no-alias) for more usage examples.


##Bonus

I've also created another validation library: [No batched queries](https://github.com/ivandotv/graphql-no-batched-queries), which limits the number of **all** queries and mutations that could be sent per request. It pairs nicely with this validation, so you could allow, for example, 3 queries to be sent and then use `noAlias` to disable duplicate queries.