Understanding a Different Kind of Attack Surface
If REST API security is about locking down a set of fixed doors, GraphQL security is about locking down one door that can be reshaped into almost any door the visitor wants. That flexibility is precisely why GraphQL needs its own security playbook, separate from REST.
A Quick Refresher on What GraphQL Actually Is
GraphQL is a query language for APIs that Facebook built internally starting around 2012 and open-sourced in 2015. Instead of exposing many fixed endpoints the way REST does, GraphQL typically exposes a single endpoint, and the client sends a query describing exactly which fields it wants back, nothing more, nothing less. Companies like GitHub, PayPal, and Intuit have adopted it specifically because it eliminates the over-fetching and under-fetching problems common in REST design.
The security trade-off: in REST, the server largely decides the shape of every response. In GraphQL, the client decides how deep and how complex a single query gets to be and that shifts a meaningful amount of risk onto how well the server enforces limits.
The Attack Patterns That Are Specific to GraphQL
Nested-query denial of service.
GraphQL lets a client nest related objects inside a query almost indefinitely,thinking “give me this album, its songs, each song’s album, that album’s songs” and so on, dozens of levels deep. A client can also request an absurd volume of a single object type in one field. Without depth and amount limits, one query can quietly exhaust server and database resources.
Injection, inherited from the backend.
GraphQL doesn’t eliminate SQL injection, NoSQL injection, OS command injection, or server-side request forgery; it just becomes the delivery pipe. Any unsanitized input that reaches a database call or system command is still exploitable, GraphQL or not.
Broken object-level authorization.
GraphQL schemas frequently let a client fetch an object directly by its ID. If the server assumes that knowing an ID equals being allowed to see it, an attacker can simply walk through sequential IDs and pull data that was never meant for them.
Batching attacks.
GraphQL natively supports bundling multiple queries or multiple requests for different object instances into one HTTP call. That’s convenient for legitimate apps and dangerous in the wrong hands: it lets an attacker enumerate user accounts or brute-force passwords, OTPs, and session tokens at high speed, all disguised as a single network request. Because most rate limiters and firewalls count requests, not operations inside a request, batching attacks routinely slip past defenses tuned for REST-style traffic.
Overly generous defaults.
Most GraphQL servers ship with introspection and tools like GraphiQL turned on out of the box, which lets anyone query the entire schema of every type, field, and mutation turning what should be a private API map into public documentation.
Defending a GraphQL API
- Validate input with GraphQL's own type system. Scalars, enums, and explicit input schemas for mutations do a lot of the input-validation work automatically, before custom code even runs.
-
Add depth limiting and amount limiting. These aren't built into GraphQL by default; most teams add libraries ( such as
graphql-depth-limitfor JavaScript, or the built-in instrumentation ingraphql-java) to cap how deep and how large a query can go. - Consider query cost analysis. Assigning a computed "cost" to each field lets the server reject expensive queries before running them though Apollo's own guidance is not to build this until you've actually tried to break your API with a nasty query and confirmed you need it.
- Set timeouts at the application layer. Infrastructure-level timeouts (on a load balancer or reverse proxy) exist too, but they're often less precise than stopping a resolver directly in code.
- Rate-limit per user or per IP, and layer this on top of not instead of object-level protections, since batching can bypass request-counting alone.
- Use server-side batching and caching (Facebook's own DataLoader tool is the reference implementation) to avoid redundant duplicate data-fetches within a single request window.
- Check authorization at every field, not just the top-level query. A documented real-world bug once involved a schema that checked permissions correctly on an "edge" but forgot to check the "node" behind it, a reminder that access control has to be enforced at every layer of a nested schema.
- Neutralize batching specifically by rate-limiting the number of distinct objects requested (even across a batch), disabling batching entirely for sensitive fields like login or OTP verification, or capping how many operations a single call can contain.
- Disable introspection and GraphiQL in production, or restrict them to authenticated internal users only.
- Return generic errors. Stack traces and debug output should never reach the client, log them internally and mask them in the response.
Quick Glossary
Key Takeaways
- GraphQL's flexibility puts more of the security burden on the server, because the client controls query shape, not just query targets.
- Depth limiting, cost analysis, and per-field authorization checks are non-negotiable in production.
- Introspection and GraphQL should be treated as development-only tools, not production defaults.
APIs Under Attack, Prophaze Secures Every Call
Discover every API, block zero‑day attacks and bots, and enforce policies at scale without slowing your developers down.
Related Content
- What Is REST API Security?
- What Is API Authorization Security?
- What Is API Authentication Security?
- What Is API Security Testing?
- What Is an API Security Audit?
- What Is Continuous API Discovery?
- What Is Runtime API Discovery?
- What Is API Authentication?
- What Is an API Response?
- What Is an API Request?