GraphQL APIs give their clients considerable flexibility to query whatever data they need. This is one of the major strengths of GraphQL, but also a source of vulnerability. When any client can query any data, malicious or careless queries can put excessive load on the server. We are releasing a solution to this problem for users of the Enterprise plan: Trusted Documents.
The idea behind Trusted Documents is simple: the flexibility offered by GraphQL is great when developing clients, but it turns into a liability in a live, production API. We can have the best of both worlds by allowing only GraphQL queries we register in an allow-list. Queries that are not part of that allow-list are rejected.
You may be familiar with the feature under different names: "persisted queries" or "persisted operations" are the most common ones. So why did we pick a different name? There is a standardization effort under way in the GraphQL over HTTP spec (issue and pull request) under the name "Trusted Documents". In a great blog post on the topic, benjie explains the correct terminology:
In GraphQL, an executable document is a text string that consists of one or more query, mutation or subscription operations and their associated fragments using the GraphQL language. People commonly refer to them as "queries", but that term is a little ambiguous — "executable document" is the precise term.
To better visualize the mechanism, it helps to look at regular GraphQL HTTP request, and compare it to the same request, but with Trusted Documents. Let's start with the regular request:
{
"query": "query UserName($id: ID!) { userById(id: $id) { name } }",
"variables": { "id": "1" }
}
Now the same request with Trusted Documents, using the Relay convention:
{
"documentId": "04b46425096bd065e55b07efd5cb6181a445deb840908c2c4e7804d6a8542e44",
"variables": { "id": "1" }
}
The GraphQL query document is replaced with an identifier, in this case a SHA-256 hash of the document. The API server transparently fetches the GraphQL document to execute from its allow-list, using the identifier. This approach comes with the same performance benefits as Automatic Persisted Queries.
There are large security and performance upsides to serving only queries from an allow-list, and few downsides. The main reason Trusted Documents are not adopted more widely is the complexity in implementing them in your organization: they have to be enforced by the API, adopted by clients of the API (who have to send a different payload), and clients need to communicate manifests (JSON files containing the documents to trust) to the API. So our focus when implementing Trusted Documents was on lowering the barrier to entry as much as possible.
You can enable Trusted Documents with a single setting in your grafbase.toml
file:
[trusted_documents]
enabled = true
And this is all that is required on the API side. Clients now need to send a document ID in their requests instead of the GraphQL query document. This will vary by client, but the major ones all have documentation (Relay, Apollo Client).
Now that the client is sending Trusted Documents requests, and the API is expecting Trusted Documents requests, we need to agree on the allow-list. Client libraries can generate JSON manifests (see the documentation for more details) that can be uploaded with the grafbase CLI's new trust
command:
$ grafbase trust my-account/my-project@main --manifest manifest.json --client-name ios-client
grafbase trust
automatically recognizes the most common manifest formats (Relay and Apollo Client). If yours is different, please contact us.
Even in production deployments, there are cases where you will want to send arbitrary GraphQL queries. This is supported through the bypass_header
settings that allow you to define a secret header name and value that will bypass the Trusted Documents checks in the API.
We are excited to help bring the security benefits of Trusted Documents to more GraphQL APIs going forward. Please head to the documentation for more details, and share your feedback with us on Discord.