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

Fictional empty cyber architecture room with glass cubes and connected nodes, abstract graph, burgundy case and network appliance on a dark table

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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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

IndicatorWhat it measuresFirst action
AccessObject and field cases denied as expectedFix the resolver
CostHeavy queries contained by policyAdjust fields and limits
ErrorsPublic responses without internal detailReduce exposed detail
RegressionClient operations replayed after changeBlock 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

Eight points for interpreting the result
PointClient consequenceSource
Request error resultAn error preventing execution yields errors and no data entry. Invalid variables follow this route even when the document parses.GraphQL 2025
Execution resultAfter 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[].messageEach error includes a developer-facing message. Give visitors useful wording without exposing internal details or sensitive identifiers.GraphQL 2025
errors[].locationsWhen 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[].pathThe 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-NullSeparate 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
extensionsResponse and error extensions are metadata maps defined by implementations. A code in extensions is not a universal GraphQL error-code catalogue.GraphQL 2025
GraphQL / HTTPThe 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

Identify the phase before choosing the message
SituationExpected shapeJourney action
Request rejected before executionNon-empty errors; data absentFix the operation or its variables. Keep an actionable error instead of automatically replaying an unchanged request.
Execution with no raised errordata present; errors absentDisplay returned data. A schema-permitted null may be normal and alone does not diagnose failure.
Execution with partial resultObject data; non-empty errorsSeparate retained fields from failing positions. Prevent confirmation that depends on unavailable evidence.
Propagation reaches the rootdata: null; non-empty errorsPresent 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.

Four list types, two distinct constraints

Four list types, two distinct constraints
preuves typeNull list allowedNull item allowedSingle item failure
[String]YesYesOne item becomes null.
[String!]YesNoThe list becomes null.
[String]!NoYesOne item becomes null; the list remains present.
[String!]!NoNoPropagation 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

Six business checks for a GraphQL viewing API
CIE scenarioEvidence to collectDecision criterion
G-ACC · Another tenant’s objectTwo 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 objectTwo 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 changeThe 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 depthTest 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 messageExamples 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 actionKeyboard 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.