Resources · 37
Secure a GraphQL API: authorization, query cost and evidence
Enforce object and field rights, bound server work and test denial paths.
· 7 min
What this guide helps achieve
- Map the exposed schema
- Exercise permissions
- Bound query cost
- Verify denials
Quick check
- Can one account read another account’s object?
- Does a sensitive field follow its parent’s access decision?
- What do depth, aliases and pagination cost together?
- What details leak through errors?
- Who observes denials and abuse?
Step-by-step method
- 01
Inventory operations
List types, fields, mutations, roles, object owners and sensitive data. Include current client queries and rarely used paths.
Deliverable: operation, role, object and field matrix.
- 02
Test each access decision
Create distinct test accounts and swap object IDs, including nested objects. Resolvers must enforce authorization before returning data.
Deliverable: reproducible allowed and denied cases.
- 03
Bound server work
Measure actual cost of fields and collections. Test depth, breadth, aliases, pagination, batching and time together, then set suitable limits.
Deliverable: cost policy and adversarial query set.
- 04
Reduce unnecessary exposure
Decide on introspection for the context, hide internal error detail and remove fields that reveal more than clients need.
Deliverable: reviewed public configuration and error responses.
- 05
Observe with restraint
Log useful technical identity, operation, cost, denial and correlation while excluding secrets and sensitive arguments. Assign alert ownership.
Deliverable: dashboard and investigation procedure.
- 06
Replay after change
When a field, role or client changes, repeat authorization and load cases. Check old queries that now traverse the revised graph.
Deliverable: regression result and release decision.
Management indicators
| Indicator | What it measures | First action |
|---|---|---|
| Access | Object and field cases denied as expected | Fix the resolver |
| Cost | Heavy queries contained by policy | Adjust fields and limits |
| Errors | Public responses without internal detail | Reduce exposed detail |
| Regression | Client operations replayed after change | Block unintended breakage |
Common pitfalls
- Trusting endpoint access alone
- Limiting depth but ignoring breadth
- Forgetting nested-field authorization
- Logging arguments with sensitive data
Frequently asked questions
Does GraphQL remove ordinary API authorization?
No. Every object and field access still needs an identity and context based decision.
Is disabling introspection sufficient?
No. It may reduce schema exposure but does not fix access or query cost.
Is a depth limit enough?
No. Aliases, breadth, lists and field-specific cost also matter.
GraphQL: errors, lists and partial data
How should a client read a GraphQL response when part of the data fails? Start with the processing phase, then follow Non-Null constraints to the first position that permits null. A screen that displays success simply because transport returned can hide an incomplete record. This dossier connects schema, JSON result and interface decisions through a fictional evidence-viewing scenario.
Reference: the published September 2025 edition, reviewed on 23 October 2026. The eight rules below are a CIE editorial selection, not a conformance certification. All scenarios and sample data are fictional.
GraphQL Specification · September 2025 · Published edition · September 2025
Eight points for interpreting the result
| Point | Client consequence | Source |
|---|---|---|
Request error result | An error preventing execution yields errors and no data entry. Invalid variables follow this route even when the document parses. | GraphQL 2025 |
Execution result | After execution, data is present. Its value may be a partial object or null. With no raised error, errors must be absent rather than an empty list. | GraphQL 2025 |
errors[].message | Each error includes a developer-facing message. Give visitors useful wording without exposing internal details or sensitive identifiers. | GraphQL 2025 |
errors[].locations | When a document position can be associated with an error, locations should give line and column starting at 1. These are not data paths. | GraphQL 2025 |
errors[].path | The path uses response names, including aliases, and zero-based integer indices. It keeps the failing position even when a parent is subsequently replaced with null. | GraphQL 2025 |
List / Non-Null | Separate list nullability from item nullability. A failing non-null item nulls the list; a non-null list propagates the error further to its parent. | GraphQL 2025 |
extensions | Response and error extensions are metadata maps defined by implementations. A code in extensions is not a universal GraphQL error-code catalogue. | GraphQL 2025 |
GraphQL / HTTP | The GraphQL specification does not define transport. GraphQL over HTTP is identified here as a working draft; test its media-type negotiation separately. | HTTP draft |
Identify the phase before choosing the message
| Situation | Expected shape | Journey action |
|---|---|---|
| Request rejected before execution | Non-empty errors; data absent | Fix the operation or its variables. Keep an actionable error instead of automatically replaying an unchanged request. |
| Execution with no raised error | data present; errors absent | Display returned data. A schema-permitted null may be normal and alone does not diagnose failure. |
| Execution with partial result | Object data; non-empty errors | Separate retained fields from failing positions. Prevent confirmation that depends on unavailable evidence. |
| Propagation reaches the root | data: null; non-empty errors | Present an unavailable state for this operation. Do not silently recycle a response belonging to another case. |
GraphQL nullability simulator
The model fixes a query using affaire as the alias of dossier. The case contains reference: String! and preuves, a three-item String list. statut: String! is a root sibling. Items are A-01, A-02 and A-03 before the injected failure. Choose the preuves type, dossier nullability and failing item.
query Demo {
affaire: dossier { reference preuves }
statut
}Local simulation of one item-resolution error on this fixed schema. No API call, query parser, permission test, cost calculation or HTTP verdict. Nested types, subscriptions and incremental delivery are outside the model. The JSON illustrates propagation and contains no real investigation data.
Fictional JSON result
Four list types, two distinct constraints
| preuves type | Null list allowed | Null item allowed | Single item failure |
|---|---|---|---|
| [String] | Yes | Yes | One item becomes null. |
| [String!] | Yes | No | The list becomes null. |
| [String]! | No | Yes | One item becomes null; the list remains present. |
| [String!]! | No | No | Propagation reaches dossier, then the root depending on its type. |
Eight reference results, readable without JavaScript
G01 · [String] · affaire: Dossier
Only item 1 becomes null; the other two items and the case remain readable.
{
"data": {
"affaire": {
"reference": "DEMO-01",
"preuves": [
"A-01",
null,
"A-03"
]
},
"statut": "fictif"
},
"errors": [
{
"message": "Illustrative item resolution failure",
"path": [
"affaire",
"preuves",
1
]
}
]
}Acceptance check : Check the received JSON and retain errors[0].path = ["affaire", "preuves", 1], even when affaire is replaced by null.
G02 · [String!] · affaire: Dossier
The preuves list becomes null; the case reference and status remain readable.
{
"data": {
"affaire": {
"reference": "DEMO-01",
"preuves": null
},
"statut": "fictif"
},
"errors": [
{
"message": "Illustrative item resolution failure",
"path": [
"affaire",
"preuves",
1
]
}
]
}Acceptance check : Check the received JSON and retain errors[0].path = ["affaire", "preuves", 1], even when affaire is replaced by null.
G03 · [String]! · affaire: Dossier
Only item 1 becomes null; the other two items and the case remain readable.
{
"data": {
"affaire": {
"reference": "DEMO-01",
"preuves": [
"A-01",
null,
"A-03"
]
},
"statut": "fictif"
},
"errors": [
{
"message": "Illustrative item resolution failure",
"path": [
"affaire",
"preuves",
1
]
}
]
}Acceptance check : Check the received JSON and retain errors[0].path = ["affaire", "preuves", 1], even when affaire is replaced by null.
G04 · [String!]! · affaire: Dossier
The affaire case becomes null; the sibling statut field remains available.
{
"data": {
"affaire": null,
"statut": "fictif"
},
"errors": [
{
"message": "Illustrative item resolution failure",
"path": [
"affaire",
"preuves",
1
]
}
]
}Acceptance check : Check the received JSON and retain errors[0].path = ["affaire", "preuves", 1], even when affaire is replaced by null.
G05 · [String] · affaire: Dossier!
Only item 1 becomes null; the other two items and the case remain readable.
{
"data": {
"affaire": {
"reference": "DEMO-01",
"preuves": [
"A-01",
null,
"A-03"
]
},
"statut": "fictif"
},
"errors": [
{
"message": "Illustrative item resolution failure",
"path": [
"affaire",
"preuves",
1
]
}
]
}Acceptance check : Check the received JSON and retain errors[0].path = ["affaire", "preuves", 1], even when affaire is replaced by null.
G06 · [String!] · affaire: Dossier!
The preuves list becomes null; the case reference and status remain readable.
{
"data": {
"affaire": {
"reference": "DEMO-01",
"preuves": null
},
"statut": "fictif"
},
"errors": [
{
"message": "Illustrative item resolution failure",
"path": [
"affaire",
"preuves",
1
]
}
]
}Acceptance check : Check the received JSON and retain errors[0].path = ["affaire", "preuves", 1], even when affaire is replaced by null.
G07 · [String]! · affaire: Dossier!
Only item 1 becomes null; the other two items and the case remain readable.
{
"data": {
"affaire": {
"reference": "DEMO-01",
"preuves": [
"A-01",
null,
"A-03"
]
},
"statut": "fictif"
},
"errors": [
{
"message": "Illustrative item resolution failure",
"path": [
"affaire",
"preuves",
1
]
}
]
}Acceptance check : Check the received JSON and retain errors[0].path = ["affaire", "preuves", 1], even when affaire is replaced by null.
G08 · [String!]! · affaire: Dossier!
data becomes null: the chain of non-null response positions reaches the root.
{
"data": null,
"errors": [
{
"message": "Illustrative item resolution failure",
"path": [
"affaire",
"preuves",
1
]
}
]
}Acceptance check : Check the received JSON and retain errors[0].path = ["affaire", "preuves", 1], even when affaire is replaced by null.
Six business checks for a GraphQL viewing API
| CIE scenario | Evidence to collect | Decision criterion |
|---|---|---|
| G-ACC · Another tenant’s object | Two test identities and the same case ID, without production data. | The service enforces access on the object, relations and fields; possessing an ID grants no access. |
| G-ALIAS · Two aliases, one object | Two aliased selections and correlated traces in an authorized environment. | Aliases change neither access scope nor cost policy. The client links each error to the received response name. |
| G-CACHE · Account change | The sign-out journey followed by sign-in as another test account. | Cached data does not cross identity changes. Partial responses do not present an old proof as newly read. |
| G-COST · Volume and depth | Test measurements of cardinality, depth, time and service-specific quotas. | Depth alone is insufficient: bound list size, expensive fields and grouped operations. No universal threshold is invented. |
| G-ERROR · Public message | Examples of visitor-facing messages and operator-only traces. | Expose no stack, secret, table name or sensitive evidence. A non-sensitive correlation identifier supports troubleshooting. |
| G-SXO · Missing evidence blocks an action | Keyboard and screen-reader journey with a required field failing. | Announce the partial state, keep retained data readable, and explain then prevent confirmation that depends on the unavailable field. |
OWASP · GraphQL Cheat Sheet · Living guidance consulted 2026-10-23
A partial response proves neither a leak nor correct protection. Only service-side authorization tests can verify access. Link each acceptance case to the deployed schema, its version and client observations: data controls, interface resilience and transport need coordinated checks with distinct criteria.
Provenance, edition and licence
GraphQL Specification · September 2025 · reviewed 2026-10-23. CIE: original explanations, fictional scenarios and limited educational model.
GraphQL HTML SHA-256: 39fb3aeffd9b5acebb0830ddc6dcc243c1280a9f0e977897ae59c5cc165511d6
HTTP draft HTML SHA-256: 90a686f8251073b05e105697c6028288520d0c0fa6442924c0d1751cad3f14ac
GraphQL over HTTP · Current Working Draft · Working draft consulted 2026-10-23 · may change
GraphQL · Copyright and Licensing · Open Web Foundation Agreement 1.0
Official references
References consulted: . The method and worksheet propose checks to adapt to your context; they do not constitute certification.
