Key Takeaways
- GraphQL sends every request to one endpoint and lets the client define the query, so tools that only count HTTP requests miss most GraphQL abuse. Effective defenses inspect the query itself.
- OWASP's GraphQL Cheat Sheet groups the main risks into injection, denial of service, broken authorization, batching attacks, and insecure default configurations.
- Batching lets one HTTP request carry many operations. OWASP notes this can bypass request-based rate limits and evade WAFs, IDS/IPS and SIEMs that see only a single request.
- Gartner research, as reported by Apollo GraphQL in 2026, projects 60% of enterprises will run GraphQL in production by 2028, up from under 40% in 2026, so the attack surface is growing.
- Introspection, GraphiQL and verbose error messages should be disabled or restricted in production.
- Authorization must be enforced in resolvers. A WAAP adds depth, cost and batch limits, bot mitigation and endpoint discovery in front of them, but cannot replace object-level checks.
GraphQL API security best practices come down to ten controls: limit query depth and cost, control batching and rate limits, restrict introspection, authorize every object and field, validate inputs, use trusted documents, mask errors, inventory every endpoint, and enforce it all behind a GraphQL-aware WAAP. For teams evaluating a GraphQL API security solution, see our buyer’s guide. This guide explains each control, why it matters, and whether it belongs in your GraphQL server or at the edge.
If you are securing GraphQL endpoints in production, the hard part is not knowing these controls exist. It is knowing where each one has to run. Some, like resolver-level authorization, must live in your code. Others, like operation-level throttling, bot mitigation and endpoint discovery, are far easier to enforce consistently at a WAAP (Web Application and API Protection) layer such as the Prophaze API security platform.
Why GraphQL Needs Its Own Security Model
GraphQL is an open source query language that lets clients ask for exactly the data they want from a single endpoint. That flexibility is why so many teams adopt it, and Gartner research reported by Apollo GraphQL projects 60% of enterprises will use it in production by 2028, up from less than 40% in 2026. So is GraphQL still relevant in 2026? Yes, and growing.
It is also the downside of GraphQL. In REST, one URL and one HTTP method map to one operation, so firewalls, rate limits and logs are built around that relationship. In GraphQL, nearly every call is a POST to /graphql, and the real risk sits inside the query body. A single request can nest dozens of levels deep, ask for thousands of records, or bundle many operations at once. Prophaze’s GraphQL API security solution buyer’s guide covers how to evaluate vendors. This article covers the controls themselves.
For a broader view of the attack patterns hitting web and API layers this year, see our Q2 2026 Application Threat Landscape Report.
GraphQL Security Vulnerabilities to Understand First
According to the OWASP GraphQL Cheat Sheet, these are the attack categories that matter most for GraphQL:
- Injection: user-supplied arguments flow into database queries, HTTP calls and OS commands, so SQL, NoSQL, OS command injection and SSRF are all possible.
- Denial of service: deeply nested or very large queries can exhaust CPU, memory and downstream services.
- Broken authorization: including IDOR, Broken Object Level Authorization (BOLA) and Broken Function Level Authorization (BFLA).
- Batching attacks: a GraphQL-specific form of brute force that packs many attempts into one request.
- Insecure defaults: introspection, GraphiQL and excessive error messages left on in production.
Most of these map directly to the OWASP API Security Top 10. For a summary of what changed in that list, see Prophaze’s OWASP API Security Top 10 updates.
Where Each Control Belongs: GraphQL Server vs. WAAP
Not every control can be pushed to the edge, and some cannot be done well in application code alone. Use this table to decide where each one runs.
The pattern is consistent: the server enforces what only it can know (who owns which object), and the WAAP enforces what should be uniform across every service (limits, discovery, abuse detection).
10 GraphQL API Security Best Practices to Enforce Behind a WAAP
1. Set GraphQL Query Depth Limiting
Query depth is how many levels of nested objects a single query traverses. By default it can be unlimited, which lets an attacker write something like this:
query {
user(id: "1") {
friends { friends { friends { friends { name } } } }
}
}
Each level multiplies the work your resolvers and database do. GraphQL does not enforce depth natively, so you add it:
graphql-depth-limitfor JavaScript,MaxQueryDepthInstrumentationfor graphql-java. Set the limit from the deepest query your legitimate clients actually send, plus modest headroom, then enforce the same ceiling at the WAAP so bad queries never reach your origin. 2. Analyze Query Cost to Prevent GraphQL Denial of Service
Depth alone is not enough for preventing GraphQL denial of service. A shallow query can still ask for a huge list, and a modest-depth query can trigger expensive joins. Query cost analysis assigns a cost to each field or type and rejects operations that exceed a budget before they run.
Pair it with three simpler limits recommended by OWASP: cap the amount requested per list (for example,
first: 50rather thanfirst: 99999999), paginate every list field, and set timeouts at the application layer, infrastructure layer, or both. Application-level timeouts are usually more effective because the resolver can be stopped mid-execution, while infrastructure timeouts are easier to bypass. 3. Apply GraphQL Rate Limiting Per Operation, Not Per Request
Traditional API gateway security counts HTTP requests. In GraphQL, that count is misleading, because one request can carry many operations. Aliases make this easy:
query {
a: login(user: "alice", pass: "password1") { token }
b: login(user: "alice", pass: "password2") { token }
c: login(user: "alice", pass: "password3") { token }
}
That is three password guesses in one HTTP call. OWASP warns batching can be used to brute force passwords, OTPs and session tokens, and that it will likely slip past request-based rate limits and security tools that see just one request.
Effective GraphQL rate limiting therefore counts operations and cumulative cost per client, limits how many operations can run in one batch, and disables batching entirely for sensitive objects such as login and OTP verification. Layer bot mitigation techniques on top so automated clients are identified by behavior, not just IP address.
4. Close GraphQL Introspection Risks in Production
Introspection lets any client ask a GraphQL server to describe its entire schema: types, fields, mutations and deprecated fields, sometimes including private ones. That is invaluable in development and a free reconnaissance map in production.
The OWASP guidance is to disable introspection and GraphiQL in any production or publicly accessible environment, or restrict them to authenticated, authorized users if external developers need them. Two details are easy to miss. First, disabling introspection does not stop attackers from guessing field names. Second, many servers return “Did you mean…?” suggestions for near-miss field names, which leaks schema details even when introspection is off. Disable field suggestions where your server supports it.
5. Enforce Authorization on Every Object and Field
Authentication says who the caller is. Authorization says what they can see. In GraphQL, a request usually includes an object ID, and a common mistake is assuming that anyone who has the ID should have access. That is Broken Object Level Authorization, and it is the most damaging of the GraphQL security vulnerabilities in practice.
Put checks in resolvers, not only at the top-level route, and cover both edges and nodes of the graph. OWASP references a real bug report where nodes lacked authorization checks that edges had. Also check whether your schema exposes
nodeornodesroot fields, which can allow direct object access by ID. Field-level rules matter too: a publicUsertype should not return an internal field just because a query asked for it. Prophaze’s overview of broken access control in APIs goes deeper on testing for these flaws.
A WAAP cannot know who owns which record. What it can do is flag the pattern around the flaw: sequential ID enumeration, unusual object-request volume from one client, or access outside a client’s normal behavior.
6. Validate Inputs and Block Injection
GraphQL enforces basic types through its schema, but not ranges, formats or business rules. Arguments still flow into SQL, NoSQL, HTTP calls and OS commands, so injection remains a real risk. OWASP’s guidance is to validate against an allowlist, use specific scalars and enums, write custom validators for complex rules, define input types for mutations, and use parameterized statements for anything passed to another interpreter.
At the WAAP layer, inspect arguments and variables inside the query body, not just the URL, for injection payloads. Reject invalid input gracefully without revealing how your validation works.
7. Secure GraphQL Endpoints With Trusted Documents
For private or internal APIs where clients are known, the strongest control is to stop accepting arbitrary queries at all. Trusted documents (also called persisted queries or an operations allowlist) work like this: approved operations are registered at build time, and clients send an identifier or hash instead of full query text. Anything not on the list is rejected.
This eliminates most query-shape attacks in one step, including depth and cost abuse, because attackers cannot submit queries you have not approved. It is less practical for public APIs that must accept custom queries, which is why the other controls still matter. Enforcing the allowlist at the WAAP makes rejection happen before traffic reaches your servers.
8. Mask Errors and Log Everything Internally
GraphQL servers in development mode return detailed errors and stack traces. In production, that gives attackers a look at your framework, file paths and query internals. Turn off debug mode, return generic errors to clients, and send the full detail to internal logs where your team can use it. In Apollo Server, for example, that means disabling debug and setting
NODE_ENVto production.
Keep those logs useful for security work too. Record operation names, cost, client identity and rejection reasons, so an audit or incident review can reconstruct what a client attempted.
9. Discover Every GraphQL Endpoint, Including Shadow Ones
You cannot protect an endpoint you do not know exists. Staging schemas left reachable, debug endpoints, and services deployed without review all create unmonitored GraphQL surface area. Inventory should come from live traffic, not a spreadsheet: observe which paths receive GraphQL operations, compare them with your registered APIs, and alert on anything new. Prophaze’s guide to shadow API discovery explains how runtime discovery closes this gap, and it is one of the strongest reasons to enforce GraphQL security at a WAAP rather than only inside individual services.
10. Enable GraphQL WAF Protection With API Firewall Rules
The final control ties the others together. GraphQL WAF protection means the firewall parses the GraphQL operation, not just the HTTP envelope, and applies rules based on what it finds. A generic rule set sees “POST /graphql” for every request. A GraphQL-aware one sees depth, cost, batch size, operation type and whether the operation was pre-approved.
Here is what GraphQL API firewall rules look like as logic. This is illustrative pseudo-configuration, not vendor syntax:
BLOCK if operation.depth > MAX_DEPTH
BLOCK if operation.cost > MAX_COST
BLOCK if batch.size > MAX_BATCH on /graphql
BLOCK if operation targets __schema or __type and client is unauthenticated
BLOCK if operation.hash not in trusted_documents
BLOCK if operation targets login or OTP fields and batch.size > 1
THROTTLE if client.cumulative_cost > BUDGET within window
Combined with bot mitigation and Layer 7 DDoS protection, this gives one consistent enforcement layer across every GraphQL service. If you run GraphQL on Kubernetes, a Kubernetes-native WAAP applies the same rules across clusters without reimplementing them per service. For a comparison of the categories involved, see WAAP vs WAF vs RASP.
Why Is a WAAP the Right Place to Enforce GraphQL Security?
A WAAP gives you one enforcement point for the controls that should be identical everywhere: depth and cost limits, batch limits, introspection blocking, trusted-document allowlisting, bot mitigation and endpoint discovery. Your GraphQL server still owns authorization, input validation and error handling, because only it has the context to do them correctly. Treat the two layers as complementary, and test both before an attacker does.
To see how these controls map to Prophaze’s platform, review the API security datasheet. To estimate what unmitigated API abuse could cost against a platform investment, use the WAAP security ROI calculator.
Protect Your GraphQL APIs Today
If your GraphQL API has not been reviewed against these ten controls, start with introspection, depth and cost limits, and batching, since they are the fastest to check. Then put a GraphQL-aware layer in front of everything else. Protect your GraphQL APIs in 15 minutes with the Prophaze API security platform.
Frequently Asked Questions (FAQ)
1. Is GraphQL a security risk?
GraphQL is not inherently less secure than REST, but its defaults and design create different risks. A single endpoint, client-defined queries, introspection and batching mean attackers can cause denial of service, enumerate data or brute force credentials in ways that request-based controls miss. With depth and cost limits, resolver-level authorization and production hardening, it can be run securely.
2. Is GraphQL still relevant in 2026?
Yes. Gartner research reported by Apollo GraphQL projects 60% of enterprises will use GraphQL in production by 2028, up from less than 40% in 2026. Adoption is growing in microservice and AI-integrated architectures, which makes securing it a current priority rather than a legacy concern.
3. Should I disable introspection in production?
For internal or private APIs, yes, disable it system-wide. For public APIs, restrict it to authenticated and authorized users who need it. Also disable GraphiQL and “did you mean” field suggestions, since these can reveal schema details even when introspection is off.
4. Is query depth limiting enough to stop GraphQL denial of service?
No. A shallow query can still request enormous lists or trigger expensive resolvers. Combine depth limits with query cost analysis, pagination caps, timeouts and batch limits. For known clients, trusted documents remove arbitrary queries entirely.
5. How do I test a GraphQL API for security issues?
Test for the categories OWASP lists: injection, denial of service through deep or large queries, broken object and field authorization, batching abuse and insecure defaults such as introspection. OWASP points to scanners like InQL, which can generate queries and mutations from a schema, for automating parts of this. Retest after every schema change.