Last updated October 2026
GraphQL security requires defending against a class of vulnerabilities unique to its flexible query language—deeply nested queries that exhaust server resources, introspection endpoints that expose your entire schema to attackers, batch queries that bypass rate limiting, and field-level authorisation gaps that leak sensitive data. Unlike REST APIs where each endpoint has a defined scope, a single GraphQL endpoint accepts arbitrary queries, making it essential to implement query analysis, depth limiting, cost analysis, and fine-grained access controls before exposing a GraphQL API to the internet.
Why Is GraphQL Security Different from REST API Security?
REST APIs expose fixed endpoints with predictable responses. A GET /users/123 endpoint returns one user’s data, and security is straightforward—authenticate the request, authorise access to user 123, validate input. GraphQL changes the dynamic fundamentally:
- Flexible queries: Clients control what data they receive. A malicious client can request deeply nested relationships (user → posts → comments → authors → posts → comments…) creating exponential data fetching.
- Single endpoint: All operations go through one URL (
/graphql), making traditional URL-based rate limiting and WAF rules less effective. - Schema as documentation: GraphQL introspection reveals your entire API schema—every type, field, argument, and relationship—by default. This is documentation for developers but also a reconnaissance goldmine for attackers.
- Batched operations: GraphQL allows sending multiple operations in a single HTTP request, potentially bypassing per-request rate limits.
How Do Depth and Complexity Attacks Work?
A depth attack exploits recursive relationships in your schema. If your schema allows User → friends → friends → friends…, an attacker can send a query nested 50 levels deep. Each level multiplies the database queries, potentially fetching millions of records and crashing your server.
Query depth limiting: Set a maximum query depth (typically 7-10 levels). Libraries like graphql-depth-limit (Node.js) or equivalent in other languages reject queries exceeding the limit before execution. Choose a depth that covers your legitimate UI requirements plus a small buffer.
Query complexity/cost analysis: Assign a cost to each field based on its resolver’s computational expense. A scalar field (name, email) costs 1; a list field (posts) costs 10 × the number of items; a nested relationship (posts → comments) multiplies costs. Reject queries whose total cost exceeds a threshold. Libraries like graphql-query-complexity and graphql-validation-complexity implement this.
Example: A legitimate dashboard query might have complexity 500. A malicious deeply nested query might have complexity 500,000. Set your limit at 1,000-5,000 depending on your API’s requirements.
Should You Disable GraphQL Introspection in Production?
Yes, disable introspection in production. Introspection allows any client to run an introspection query and receive your complete schema—every type, every field, every argument, every enum value, every deprecated field. This tells an attacker exactly what data is available, what fields might contain sensitive information, and how to craft targeted queries.
In development and staging environments, keep introspection enabled for developer tooling (GraphiQL, Apollo Explorer). In production, disable it. Most GraphQL servers have a configuration option: Apollo Server uses introspection: false, Hasura uses environment variables, and custom servers can add a validation rule that rejects introspection queries.
Note: disabling introspection is a defence-in-depth measure, not a complete solution. Attackers can still discover your schema through field suggestion features, error messages that reveal field names, and brute-force field name guessing. Always implement authorisation regardless of introspection settings.
How Do You Implement Field-Level Authorisation?
One of GraphQL’s biggest security pitfalls is authorisation gaps at the field level. A User type might have public fields (name, bio) and private fields (email, phone, address). Without field-level authorisation, any authenticated user can query any other user’s private fields.
Implementation approaches:
- Resolver-level checks: Each resolver checks whether the requesting user has permission to access that specific field for that specific object. This is the most granular approach but can lead to scattered authorisation logic.
- Schema directives: Define custom directives like
@auth(requires: ADMIN)on sensitive fields in the schema. A middleware processes these directives before resolver execution. - DataLoader with authorisation: Centralise data fetching through DataLoaders that enforce access policies. When a resolver requests a user’s email, the DataLoader checks permission before returning the value.
- Policy engines: Use external policy engines like Open Policy Agent (OPA) or Casbin that evaluate GraphQL queries against defined policies. This separates authorisation logic from application code.
Critical principle: never rely on the client (your frontend application) to avoid querying sensitive fields. Assume every possible query will be sent and enforce authorisation server-side.
How Do You Prevent Batch Query Abuse?
GraphQL allows sending arrays of operations in a single HTTP request. An attacker can send 1,000 login mutations in one request, each with a different password, effectively brute-forcing authentication while bypassing per-request rate limits.
Mitigations:
- Limit batch size: Reject requests containing more than N operations (typically 5-10 for production APIs).
- Per-operation rate limiting: Count each operation in a batch individually against rate limits, not the HTTP request.
- Disable batching entirely: If your client applications do not use query batching, disable it at the server level. This is the simplest and most secure approach.
- Operation-specific limits: Apply stricter rate limits to sensitive mutations (login, password reset, payment) regardless of how they are batched.
What Other GraphQL-Specific Vulnerabilities Should You Address?
- Injection through variables: GraphQL variables are typed, which provides some protection, but String variables passed to resolvers that construct database queries can still enable SQL injection. Always use parameterised queries in resolvers.
- Denial of service through aliases: An attacker can alias the same expensive field 1,000 times in a single query:
a1: expensiveField, a2: expensiveField, a3: expensiveField…. The complexity analyser should count each alias as a separate cost unit. - Error message leakage: Default error messages may reveal internal details—database table names, file paths, stack traces. Configure your GraphQL server to return generic error messages in production while logging detailed errors server-side.
- Subscription abuse: GraphQL subscriptions open WebSocket connections. Apply the same WebSocket security measures discussed earlier: authentication, connection limits, message rate limiting.
Frequently Asked Questions
Is GraphQL less secure than REST?
Not inherently, but it has a larger default attack surface. REST APIs are secure by omission—each endpoint exposes only what you explicitly build. GraphQL is exposed by permission—the schema defines all possibilities, and you must restrict what clients can access. Both are equally secure when properly implemented, but GraphQL requires more deliberate security configuration.
Should I use persisted queries in production?
Yes, for client applications you control (your own frontend, mobile app). Persisted queries allow only pre-approved queries registered at build time. The client sends a query hash instead of the full query text, and the server executes only registered queries. This eliminates arbitrary query attacks entirely for your own clients. Third-party API consumers still need the full query capabilities with appropriate limits.
How do I rate limit a single GraphQL endpoint?
Traditional per-endpoint rate limiting (e.g., 100 requests/minute to /graphql) is too coarse. Instead, implement cost-based rate limiting: assign each user a “query budget” per time window (e.g., 10,000 complexity points per minute). Each query consumes budget proportional to its computed complexity. This allows lightweight queries to run frequently while limiting expensive queries. Libraries like graphql-rate-limit implement this pattern.
Can WAFs protect GraphQL APIs?
Standard WAFs have limited GraphQL awareness. They can inspect the HTTP request/response but may not parse GraphQL query syntax. Specialised GraphQL security tools like GraphQL Armor, GraphQL Shield, and Escape.tech provide GraphQL-aware protection including query analysis, schema monitoring, and anomaly detection. For Indian enterprises with compliance requirements (RBI data protection, DPDP Act), combine a WAF with GraphQL-specific security tooling.
What logging should I implement for GraphQL security monitoring?
Log every query (or at least a hash and complexity score), the authenticated user, response time, error codes, and any security events (rate limit hits, depth limit violations, authorisation failures). Avoid logging sensitive variable values (passwords, tokens). Monitor for patterns: repeated failed authorisation attempts (enumeration), sudden complexity spikes (DoS probing), and introspection queries from unexpected sources. Set up alerts on these patterns in your monitoring tool.






