Skip to main content

Exograph supports trusted documents

· 7 min read
Ramnivas Laddad
Co-founder @ Exograph

Exograph 0.7 introduces support for trusted documents, also known as "persisted documents" or "persisted queries". This new feature makes Exograph even more secure by allowing only specific queries and mutations, thus shrinking the API surface. It also offers other benefits, such as improving performance by reducing the payload size.

Trusted documents are a pre-arranged way for clients to convey "executable documents" (roughly queries and mutations, but see the GraphQL spec and Exograph's documentation for a distinction) they would be using. The server then allows executing only those documents.

tip

For a good introduction to trusted documents, including the reason to prefer the term "trusted documents", please see the GraphQL Trusted Documents. Exograph's trusted document follows the same general principles outlined in it. We hope that someday a GraphQL specification will standardize this concept.

This blog post explains what trusted documents are and how to use them in Exograph.

Conceptual Overview

Why Trusted Documents?

Consider the todo application we developed in an earlier blog (source code). This single-screen application allows creating, updating, and deleting todos besides viewing by their completion status. The application only needs the following queries and mutations (the application uses fragments, but we will keep it simple here).

The client application needs two queries to get todos by their completion status:

Query to get todos by completion status
query getTodosByCompletion($completed: Boolean!) {
todos(where: { completed: { eq: $completed } }, orderBy: { id: ASC }) {
id
completed
title
}
}
Query to get all todos
query getTodos {
todos(orderBy: { id: ASC }) {
id
completed
title
}
}

The application also needs mutations to create, update, and delete a todo:

Mutation to create a todo
mutation createTodo($completed: Boolean!, $title: String!) {
createTodo(data: { title: $title, completed: $completed }) {
id
}
}
Mutation to update a todo
mutation updateTodo($id: Int!, $completed: Boolean!, $title: String!) {
updateTodo(id: $id, data: { title: $title, completed: $completed }) {
id
}
}
Mutation to delete a todo
mutation deleteTodo($id: Int!) {
deleteTodo(id: $id) {
id
}
}

Contrast this with what the server offers. For example, the server provides:

  • Querying a todo by its ID, but the client application doesn't need it, since it doesn't display a single todo.
  • Flexible filtering, but the client application needs only by completion status.
  • Sorting by other fields or a combination, but the client application only sorts by the ID in ascending order.
  • Creating, updating, and deleting in bunk, but the client application only mutates one todo at a time.

While Exograph (and other GraphQL servers) offer flexible field selection for all queries and mutations, each client query or mutation only needs a specific set of fields.

What are Trusted Documents?

Because the client app requires only specific queries and mutations, it can inform the server using trusted documents. These documents are files with all the executable queries and mutations the client app needs. For instance, in the case of the todo app, a tool would create a file like this:

{
"2a...": "query getTodosByCompletion($completed: Boolean!) ...",
"3b...": "query getTodos ...",
"4c...": "mutation createTodo($completed: Boolean!, $title: String!) ...",
"5d...": "mutation updateTodo($id: Int!, $completed: Boolean!, $title: String!) ...",
"6e...": "mutation deleteTodo($id: Int!) ..."
}

Here, the key is a hash (SHA256, for example) of the query or mutation, and the value is the query or mutation itself.

Benefits of using Trusted Documents

Using trusted documents reduces the effective API surface area and offers several benefits, such as:

  • Preventing attackers from executing arbitrary queries and mutations.
  • Reducing the bandwidth by sending only the hash of the query instead of the query itself.
  • Providing a focus for testing the actual queries and mutations used by the client application.
  • Allowing server-side optimizations such as caching query parsing and pre-planning query execution.

You may wonder if there is a different way to achieve the same benefits. Specifically, couldn't the server provide a narrower set of queries and mutations? For example, could it not offer the API "get a todo by its ID"? While feasible, this approach undermines one of GraphQL's strengths: enabling client-side development without continuous server team involvement. For example, the client application may want to offer a route such as todos/:id to show a particular todo. It will then need a todo by its ID. Without this query, the client must request the server team to add it, consuming time and resources. Trusted documents streamline this process. They allow the client to effortlessly express requirements, like fetching todos by ID. Thus, the server should maintain a reasonably flexible API while clients communicate their needs through trusted documents.

Using Trusted Documents in Exograph

Like any other feature in Exograph, using trusted documents is straightforward. All you need to do is put the trusted documents in the trusted-documents directory! Exograph will automatically use them.

Workflow

A typical workflow to use trusted documents in Exograph is as follows:

  1. Generate trusted documents: In this (typically automated) step, a tool such as graphql-codegen and generate-persisted-query-manifest examines the client application and generates the trusted documents.
  2. Convey the server: Exograph expects trusted documents in either format in the trusted-documents directory. The server will now accept only the trusted documents.
  3. Set up the client: The client may now send the hashes of the executable documents instead of the full text. To do this, The client can use the persistedExchange with URQL or @apollo/persisted-query-lists or some custom code.

For a concrete implementation of this workflow, please see the updated todo application: Apollo version and URQL version.

Organizing Trusted Documents

Typically, you will have more than one client, each with a different set of trusted documents, connected to an Exograph server. Exograph supports this by allowing them to be placed anywhere in the trusted-documents directory. For example, you could support multiple clients and their versions by placing trusted documents in the following structure:

todo-app
├── src
│ └── ...
├── trusted-documents
│ ├── web
│ │ └── user-facing.json
│ │ └── admin-facing.json
│ └── ios
│ │ └── core.json
│ │ └── admin.json
│ └── android
│ │ └── version1.json
│ │ └── version2.json

The server will allow trusted documents in any of the files.

Using Trusted Documents in Development

If you have added the trusted-documents directory, Exograph enforces trusted documents in production mode without any way to opt out, extending its general secure by default philosophy.

In development mode, however, where exploring API is common, Exograph makes a few exceptions. When you start the server in development mode (either exo dev or exo yolo), it allows untrusted documents under the following conditions:

  • If the GraphQL playground executes an operation.
  • If the development server is stated with the --enforce-trusted-documents=false option.

In either case, it will implicitly trust any introspection query, thus allowing GraphQL Playground and tools such as GraphQL Code Generator to work.

Try it out

You can see this support in action through the updated todo application: Apollo version and URQL version.

For more detailed documentation, including how to create trusted documents and set up the client, please see the trusted documents documentation.

Let us know what you think of trusted documents support in Exograph. You can reach us on Twitter or Discord.

Share: