Feb 29

Persisted Operations: The Easiest Way to Secure & Speed up Your GraphQL API

Blog post's hero image

One of GraphQL’s strengths, beloved by front-end developers, is its flexibility. The client can specify exactly which data it wants, and it’ll get that (and only that) data back. However, this novel approach to data access opens up novel attack vectors that must be protected against.

If you have a GraphQL API and control all the clients accessing it, there is a quick way to eliminate all of them at once: persisted operations.

Persisted Operations in-depth

What are persisted operations

Persisted Operations replace the full query string with a unique identifier - a hash of the query. The client sends the hashed GraphQL query to the GraphQL server. The server looks up the hashed query, and only if it’s found in its local registry is it translated into its original query string and executed.

How do they mitigate all GraphQL-specific attack vectors?

Persisted operations are the ultimate security mechanism. Explicitly defining which operations are allowed eliminates the risk associated with the dynamic aspect of GraphQL queries. GraphQL-specific security concerns like alias exploits or massive arbitrary queries are no longer a concern. The best part is that risk is reduced without losing the flexibility of GraphQL that developers love, as queries aren’t hashed until deploy-time.

Persisted operations also have the added benefit of integrating nicely with observability tools. These tools often operate by grouping functionality by distinct endpoints like REST routes. This can be less effective with GraphQL APIs, where a single endpoint can be queried for any data. Persisted operations, however, more closely mimic the REST pattern by creating a finite set of endpoints for each query your clients use.

Performance Improvements

Persisted operations can also provide performance improvements and reduce network overhead. When a client makes a query, it only sends the hashed query (typically 32 bytes) to the server rather than the original raw query. No matter how many fields the query requests, the network overhead is 32 bytes (+ some metadata).

# Before
POST /graphql
{
user(id: 1) {
id
name
avatarUrl
posts(first: 10) {
id
title
body
slug
}
}
}
# After
POST /graphql
{"extensions":{"persistedQuery":{"version":1,"sha256Hash":"843c076a1ebceac7b116f910150633128af780224ad4caf86af9e66d3048dec3"}}}

What are you waiting for

Persisted operations are a simple way to harden your GraphQL API and improve performance without reducing the flexibility of GraphQL. You can learn more about Persisted operations and how they integrate seamlessly with Stellate over in our docs.