Skip to content

Commit 029da73

Browse files
benjieyaacovCR
authored andcommitted
docs: trusted documents (graphql#4418)
Expands on graphql#4398 by recommending the trusted documents pattern which 90+% of GraphQL users should be using.
1 parent 42574d8 commit 029da73

File tree

2 files changed

+62
-5
lines changed

2 files changed

+62
-5
lines changed

cspell.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ overrides:
3232
- subschema
3333
- subschemas
3434
- NATS
35+
- benjie
36+
- codegen
37+
- URQL
38+
- tada
3539
- Graphile
3640

3741
validateDirectives: true

website/pages/docs/going-to-production.mdx

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,58 @@ Common strategies for securing a schema include:
152152
153153
These techniques can help protect your server from accidental misuse or intentional abuse.
154154
155+
### Only allow trusted documents
156+
157+
The most reliable way to protect your GraphQL endpoint from malicious requests
158+
is to only allow operations that you trust — those written by your own
159+
engineers — to be executed.
160+
161+
This technique is not suitable for public APIs that are intended to accept
162+
ad-hoc queries from third parties, but if your GraphQL API is only meant to
163+
power your own websites and apps then it is a simple yet incredibly effective
164+
technique to protect your API endpoint.
165+
166+
Implementing the trusted documents pattern is straightforward:
167+
168+
- When deploying a website or application, SHA256 hash the GraphQL documents
169+
(queries, mutations, subscriptions and associated fragments) it contains, and
170+
place them in a trusted store the server has access to.
171+
- When issuing a request from the client, omit the document (`"query":"{...}"`) and
172+
instead provide the document hash (`"documentId": "sha256:..."`).
173+
- The server should retrieve the document from your trusted store via this hash;
174+
if no document is found or no hash is provided then the request should be
175+
rejected.
176+
177+
This pattern not only improves security significantly by preventing malicious
178+
queries, it has a number of additional benefits:
179+
180+
- Reduces network size since you're sending a hash (~64 bytes) rather than the
181+
entire GraphQL document (which can be tens of kilobytes).
182+
- Makes schema evolution easier because you have a concrete list of all the
183+
fields/types that are in use.
184+
- Makes tracking issues easier because you can tie a hash to the
185+
client/deployment that introduced it.
186+
187+
Be careful not to confuse trusted documents (the key component of which are
188+
trust) with automatic persisted queries (APQ) which are a network optimization
189+
potentially open for anyone to use.
190+
191+
Additional resources:
192+
193+
- [GraphQL over HTTP Appendix
194+
A: Persisted Documents](https://github.com/graphql/graphql-over-http/pull/264)
195+
- [GraphQL Trusted Documents](https://benjie.dev/graphql/trusted-documents) and
196+
[Techniques to Protect Your GraphQL API](https://benjie.dev/talks/techniques-to-protect) at benjie.dev
197+
- [@graphql-codegen/client-preset persisted documents](https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#persisted-documents) or [graphql-codegen-persisted-query-ids](https://github.com/valu-digital/graphql-codegen-persisted-query-ids#integrating-with-apollo-client) for Apollo Client
198+
- [Persisted queries in Relay](https://relay.dev/docs/guides/persisted-queries/)
199+
- [Persisted queries in URQL](https://www.npmjs.com/package/@urql/exchange-persisted)
200+
- [Persisted documents in gql.tada](https://gql-tada.0no.co/guides/persisted-documents)
201+
- [persisted queries with `fetch()`](https://github.com/jasonkuhrt/graffle/issues/269)
202+
155203
### Control schema introspection
156204
205+
(Unnecessary if you only allow trusted documents.)
206+
157207
Introspection lets clients query the structure of your schema, including types
158208
and fields. While helpful during development, it may be an unnecessary in
159209
production and disabling it may reduce your API's attack surface.
@@ -173,6 +223,8 @@ control as needed for your tools and implementation.
173223
174224
### Limit query complexity
175225
226+
(Can be a development-only concern if you only allow trusted documents.)
227+
176228
GraphQL allows deeply nested queries, which can be expensive to resolve. You can prevent this
177229
with query depth limits or cost analysis.
178230
@@ -405,15 +457,16 @@ Before deploying, confirm the following checks are complete:
405457
- Development-only checks are removed from the production build
406458
407459
### Schema security
408-
- Introspection is disabled or restricted in production
409-
- Query depth is limited
410-
- Query cost limits are in place
411460
- Authentication is required for requests
412-
- Authorization is enforced in resolvers
461+
- Authorization is enforced via business logic
413462
- Rate limiting is applied
463+
- Only allow trusted documents, or:
464+
- Introspection is disabled or restricted in production
465+
- Query depth is limited
466+
- Query cost limits are in place
414467
415468
### Performance
416-
- `DataLoader` is used to batch database access
469+
- `DataLoader` is used to batch data fetching
417470
- Expensive resolvers use caching (request-scoped or shared)
418471
- Public queries use HTTP or CDN caching
419472
- Schema is reused across requests (not rebuilt each time)

0 commit comments

Comments
 (0)