Persisted Operations are essentially an operation allowlist. Persisted Operations provide an additional layer of security to your GraphQL API by disallowing arbitrary queries to be performed against your APIs.
We recommend that all GraphQL APIs that only intend a specific/known set of clients to use the API should use Persisted Operations.
To best explain why you most likely should be using Trusted Documents, please check out the following resources:
They are the same thing. The intention is to compose a set of operations you expect to happen, typically during the build time of your clients, and load these onto your server. You allow only these operations to be executed through the exchange of the ID (or hash) of these operations.
Trusted Documents conveys these operations are trusted.
We use Trusted Documents and Persisted Operations interchangeably in this documentation.
You can configure graphql-protect
to enable Persisted Operations.
# ...
# Trusted documents uses the same configuration as persisted operations, as they are the same thing.
persisted_operations:
# Enable or disable the feature, disabled by default
enabled: false
# configures a '/internal/debug_trusted_documents' endpoint to print the persisted operations as json
# Make sure you dont expose this ednpoint publicly if you enable this feature!
enable_debug_endpoint: false
# Fail unknown operations, disable this feature to allow unknown operations to reach your GraphQL API
reject_on_failure: true
# Loader decides how persisted operations are loaded, see loader chapter for more details
loader:
# Type of loader to use
type: local
# Location to load persisted operations from
location: ./store
# Whether to reload persisted operations periodically
reload:
enabled: true
# The interval in which the persisted operations are refreshed
interval: 5m0s
# The timeout for the refreshing operation
timeout: 10s
# ...
graphql-protect
looks at the location specified for the loader
and looks for any *.json
files it can parse for persisted operations.
These loaders can be specified to look at local directories, or remote locations like GCP buckets.
graphql-protect
will load the persisted operations from the location and update its internal state with any new operations.
Currently we have support for the following loaders, specified by the type
field in the loader configuration:
local
- load persisted operations from local file system, this is the default strategy. If need be this allows you to download files from an unsupported remote location to local storage, and havegraphql-protect
pick up on them.gcp
- load persisted operations from a GCP bucketnoop
- no persisted operations are loaded. This is the strategy applied when an unknown type is supplied.
To be able to parse Persisted Operations graphql-protect expects a key-value
structure for hash-operation
in the files.
any-file.json
{
"key": "query { product(id: 1) { id name } }",
"another-key": "query { hello }"
}
Once loaded, any incoming operation with a known hash will be modified to include the operations specified as the value.
We follow the APQ specification for sending hashes to the server.
Important: While we use the specification for APQ, be aware that automatically persisting unknown operations is NOT supported.
Automated Persisted Queries/Operations is essentially the same as Persisted Operations, except a client can send arbitrary operations which will be remembered by the server.
This completely removes the security benefit of Persisted Operations as any client can still send arbitrary operations. In fact, security is reduced since a malicious user could spam your endpoint with persisted operation registrations which would overflow your store and affect reliability.
For this reason we do not deem APQ a good practice, and have chosen not to support it.
In order to utilize this feature you need to generate the persisted operations that each client can perform.
This rule produces metrics to help you gain insights into the behavior of the rule.
graphql_protect_persisted_operations_result_count{state, result}
state |
Description |
---|---|
unknown |
The rule was not able to do its job. This happens either when reject_on_failure is set to false or the rule was not able to deserialize the request. |
error |
The rule caught an error during request body mutation. |
known |
The rule received a hash for which it had a known operation |
result |
Description |
---|---|
allowed |
The rule allowed the request |
rejected |
The rule rejected the request |
graphql_protect_persisted_operations_load_result_count{type, result}
type |
Description |
---|---|
local |
Loaded using the local loader |
gcp |
Loaded using the gcp loader |
noop |
Loaded using the noop loader |
result |
Description |
---|---|
success |
loading was successful |
failure |
loading produced an error |
No metrics are produced when the rule is disabled.
graphql_protect_persisted_operations_unique_hashes_in_memory_count{}
No metrics are produced when the rule is disabled.