How to Handle GraphQL Errors for a Secure API
Tags: graphql, security, webdev, api
Published: 2022-05-25 15:05:53

Error messages in GraphQL are rarely considered as a security matter... yet they 100% should!

The default error messages are way too verbose. They could disclose information about your internal infrastructure such as what database you use, or worse, leak your secret keys.

Error messages give direct feedback for hackers to run a fuzzy search (finding patterns through trial and error) on your API until they find vulnerabilities. 

For example, if the error message contains information about the database schema and the query used to retrieve user information, a hacker could figure out a way to perform a SQL injection and get the information of EVERY user in your database, bypassing any access control 🤯

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h5nz6om3i42n4az7r9ai.png)


Handling error messages properly is one of the most essential step of building a secure GraphQL API (see [9 GraphQL security best practices](https://blog.escape.tech/9-graphql-security-best-practices/)).


Let's see how GraphQL errors work, how to mask verbose messages but also how to create your own custom errors by leveraging the flexible GraphQL spec.

_This was originally posted on [blog.escape.tech](https://blog.escape.tech/graphql-error-handling-a-security-pov/)_

## How errors work in GraphQL

GraphQL errors are fundamentally different from REST errors. You no longer rely on status codes and status texts.

According to the [latest spec](https://spec.graphql.org/October2021/#sec-Errors), the response of a GraphQL endpoint should always contain either the `data` field or the `errors` field, and in some cases, both:

![Example of error message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/njxfez8lowiem3t799n1.png)

As you can see above, an error object has the following fields:
- _message_: the error message - if you throw an error in the resolver, its message will appear here
- _locations_: contains the line and column (starting from 1) of the syntax element that is concerned with the error (in the example above, this is the "a" of author. The column is 5 for the indent but would be 1 if we remove the indent in the query)
- _path_: the path of the response field which experienced the error (firstPost → author).

Additionally, you can add the `extensions` field which is a map containing any custom field you see fit:

![https://spec.graphql.org/October2021/#example-8b658
](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a4vmmc9cnvmt14fiphjf.png)

> **Note**: it is recommended to use the extensions field for custom fields even though no error will be thrown when doing otherwise.

The code field of the extensions is usually implemented by the framework. For example, here are the [default codes in Apollo](https://www.apollographql.com/docs/apollo-server/data/errors/#error-codes):

- `GRAPHQL_PARSE_FAILED` (SyntaxError)
- `GRAPHQL_VALIDATION_FAILED` (ValidationError)
- `BAD_USER_INPUT` (UserInputError)
- `UNAUTHENTICATED` (AuthenticationError)
- `FORBIDDEN` (ForbiddenError)
- `PERSISTED_QUERY_NOT_FOUND` (PersistedQueryNotFoundError)
- `PERSISTED_QUERY_NOT_SUPPORTED` (PersistedQueryNotSupportedError)
- `INTERNAL_SERVER_ERROR` (None)

We’ll see below how you can define your own custom error at the end.

## How to mask sensitive information and stack traces

Back to the initial problem: not giving up sensitive information to consumers of your API.

The errors in GraphQL are very comprehensive: they tell you what broke, where it broke both in the query and in your code. That is great for developer experience, but clearly not intended for the end-user.

Information like the stacktrace - the path tracing all the way back to the breaking point in your code - are usually found in the logs, not in the error message.

The solution to catch these default errors before they get sent to the end-user is to use a custom `formatError` function in your GraphQL server (framework agnostic), to:

1. log the error internally for debugging,
2. update the error that you don’t want to expose to clients in production,
3. send a _user-friendly_ error.

```js
// api.js

formatError: (err) => {
	// 1. Log the error for internal debugging
    logger(err, req, Date.now())
    
    // 2. Update the error message
    if (err.message.includes('Database Error')) {
        err.message = 'Internal server error'
    }
		
	// 3. Return the error message
    return {
    	...err
        stack: process.env.NODE_ENV === 'production' ? undefined : err.stack
	}
}
```

Cool, now our API is not going to betray us.

But we can do more. 

Let’s see how we can leverage the _GraphQL spec_ we saw above to create our own custom error.

## How to write custom errors

There are three key components we can play with to build custom errors:

1. _the message_: giving more context to the user, eg. "password should have at least 6 characters",
2. _the extensions code_: an optional field, but a good practice in API error design to easily classify the different types of errors in the frontend (eg. with `⚠️ WARNING`, `❌ ERROR` or `ℹ️ INFO` callouts)
3. any other extensions field: this is where you can get creative and add any extra context, eg. the field name that cause an error in the case of a form submission.

Let's run through an example with real code!

The first step is to extend the `GraphQLError` class to create a custom error:

```ts
import {GraphQLError, GraphQLErrorExtensions} from 'graphql'

class InputValidationError extends GraphQLError {
	extensions: GraphQLErrorExtensions;
    constructor(message: string, field: string) {
        super(message);
        this.extensions = {
            statusCode: 500,
            code: "INPUT_VALIDATION_FAILED",
            field,
        };
    }
}
```
⚠️ The latest spec highly recommend putting any custom fields under `extensions`.

Now you can throw the error where necessary:

```ts
post: (_parent:null, {id}: QueryPostArgs, context: Context) => {
	if(id >= context.db.posts.length) {
    	throw new InputValidationError("Index out of range", "id")
    }
	// Fetch and return the corresponding post
}
```

Finally, make sure to return the `extensions` field if you use a custom `formatError` function:

```ts
formatError: (err) => {
	return {
    	message: err.message,
		extensions: err.extensions, // this is already returned by default
		stack: process.env.NODE_ENV === 'production' ? undefined : err.stack
	}
}
```

And voila!

![Custom error in GraphQL Playground](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/95ft24scr6elmi68o74m.png)


Alternatively, most GraphQL frameworks have a GraphQLError wrapper that lets you throw custom errors on the fly without writing new ones.

```js
import {ApolloError} from 'apollo-server-errors

throw new ApolloError('Index out of range', 'INPUT_VALIDATION_FAILED', {field: 'id'})
```

## GraphQL Security

Error messages are just one of many GraphQL vulnerabilities. If you need an in-depth scan running dozens of security tests on your GraphQL endpoints, you should try out [Escape](https://escape.tech)!

![Escape dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/acsibun5ldn9l1648j1e.png)