Locking Down GraphQL: Security Best Practices That Matter
All articles
GraphQL securityAPI securityweb securitydeveloper toolsCSP

Locking Down GraphQL: Security Best Practices That Matter

GraphQL API security best practices covering introspection, query depth, rate limiting, auth, and headers to stop abuse before it starts.

WebSentry TeamAugust 9, 20267 min read

GraphQL gives clients flexible, precise access to your data — which is exactly why it's such an attractive target. A single endpoint, a flexible query language, and one poorly configured resolver can expose your entire schema to an attacker. Unlike REST, where each endpoint is a separate attack surface, GraphQL concentrates everything into one door. If that door isn't locked properly, the blast radius is huge.

Here's what actually matters when securing a GraphQL API in production — not theoretical advice, but the specific misconfigurations that show up in real audits.

1. Disable Introspection in Production

Introspection is GraphQL's built-in "describe yourself" feature. It's fantastic for development tooling like GraphiQL or Apollo Studio, and terrible to leave exposed publicly. An attacker with introspection access can map your entire schema — every type, field, mutation, and argument — without guessing a thing.

  • Turn off introspection in production builds (most servers, like Apollo Server, allow this via a config flag: introspection: false).
  • If your team needs introspection for debugging, gate it behind an internal network, VPN, or authenticated admin route.
  • Don't rely on "security through obscurity" — if introspection is on, assume the schema is public.

Quick test

Send a standard introspection query to your production endpoint:

query IntrospectionQuery {
  __schema {
    types { name }
  }
}

If you get a full type list back, introspection is live and needs to be shut off.

2. Enforce Query Depth and Complexity Limits

Because GraphQL lets clients nest queries arbitrarily, a single malicious request can trigger enormous server-side work. This is the GraphQL equivalent of a resource-exhaustion DoS, and it doesn't require botnets — one deeply nested query can do it.

query Evil {
  user(id: "1") {
    friends {
      friends {
        friends {
          friends { name }
        }
      }
    }
  }
}
  • Depth limiting: cap query nesting (libraries like graphql-depth-limit for Node make this a few lines of config).
  • Complexity/cost analysis: assign a "cost" to each field and reject queries above a threshold. This catches wide queries, not just deep ones.
  • Timeouts: set hard execution timeouts at the resolver and server level as a backstop.

3. Rate Limit by Operation, Not Just IP

Traditional REST rate limiting (X requests per IP per minute) doesn't map cleanly to GraphQL, where one request can contain multiple operations or aliased duplicate queries designed to bypass naive limits.

  • Limit by query cost, not request count — a cheap query and an expensive one shouldn't count the same.
  • Watch for aliasing abuse, where an attacker sends the same mutation 50 times under different aliases in one request to bypass a "1 request = 1 action" limiter:
mutation {
  a1: login(user: "test1") { token }
  a2: login(user: "test2") { token }
  a3: login(user: "test3") { token }
}
  • Reject requests with excessive aliases or duplicate root fields.
  • Apply per-user and per-IP limits together, since authenticated abuse and anonymous scraping look different.

4. Get Authorization Right at the Field Level

This is where most GraphQL breaches actually happen — not at the transport layer, but in resolver logic. It's common to see auth checks applied at the query root but missing on nested fields or related-object resolvers.

  1. Never assume that because a top-level query is authenticated, every nested field is safe to return.
  2. Apply authorization checks inside each resolver that touches sensitive data, not just at the gateway.
  3. Use a consistent authorization layer (e.g., GraphQL Shield, a directive-based permission system, or a centralized policy engine) rather than scattering if (user.role...) checks across dozens of resolver files.
  4. Test object-level authorization specifically — can user A query user B's private data by supplying B's ID in an otherwise-legitimate query? This is the GraphQL version of a broken object-level authorization (BOLA) vulnerability, and it's extremely common.

5. Sanitize and Validate Inputs Like You Would for REST

GraphQL's type system gives a false sense of security. Type validation isn't the same as business-logic validation or injection protection.

  • Still validate string inputs against injection attacks if they touch raw database queries, especially with tools that allow direct DB filtering by string.
  • Enforce max lengths and formats on inputs — GraphQL won't stop someone from submitting a 2MB string into a "name" field unless you tell it to.
  • Watch for batching abuse in mutations that write to a database — validate array sizes on list inputs.

6. Don't Leak Stack Traces in Error Responses

Default GraphQL error handling in some frameworks returns verbose error messages, including stack traces, resolver file paths, and sometimes SQL fragments. This is a gift to anyone probing your API.

  • Use a formatError function (Apollo Server) or equivalent to strip internal details from production error responses.
  • Log full errors server-side, but return generic, non-identifying messages to the client.
  • Audit your error responses the same way you'd audit an HTTP 500 page — assume attackers will deliberately trigger errors to learn about your stack.

7. Secure the Transport Layer Too

It's easy to get tunnel vision on GraphQL-specific issues and forget the basics that still apply to any HTTP endpoint:

  • TLS/SSL: GraphQL endpoints must be served over HTTPS with a valid, current certificate — no exceptions, even for internal APIs.
  • CORS: a GraphQL endpoint with a wildcard Access-Control-Allow-Origin and credentials enabled is a serious cross-origin data leak risk. Lock CORS down to known origins.
  • Security headers: Content-Security-Policy, X-Content-Type-Options, and Strict-Transport-Security still matter for the surrounding app that serves your GraphQL client.
  • Cookies: if you're using cookie-based auth for GraphQL, make sure HttpOnly, Secure, and SameSite attributes are set correctly.

This is where a lot of teams lose track — they harden the schema and resolvers but never check whether the underlying SSL config, headers, or CORS policy are actually solid. Running a scan with a tool like WebSentry against the domain hosting your GraphQL endpoint will flag missing headers, weak CORS rules, or SSL misconfigurations in a couple of minutes — the kind of gaps that don't show up when you're only testing queries and mutations.

8. Monitor and Log Query Patterns

  • Log query shape (not just response times) so you can spot anomalies like sudden spikes in deeply nested queries or repeated introspection attempts.
  • Set up alerting for unusual mutation volume from a single user or IP.
  • Persist a query allowlist for public-facing clients where feasible — only pre-approved queries from your own frontend are accepted, which shuts down arbitrary query abuse entirely.

Persisted Queries as a Hardening Layer

If your GraphQL API only serves a known set of frontend clients (not third-party developers), persisted queries are one of the strongest mitigations available. Instead of accepting arbitrary query strings, the server only executes queries matching a pre-registered hash. This eliminates most injection, depth-abuse, and introspection-scraping vectors in one move, at the cost of needing a build step to register queries.

Putting It Together

A secure GraphQL setup isn't one control — it's layered: introspection disabled, depth and complexity limits enforced, field-level authorization checked, inputs validated, errors sanitized, and the transport layer (TLS, headers, CORS, cookies) locked down underneath it all. Teams that focus only on GraphQL-specific hardening while ignoring the surrounding HTTP security posture leave an easy way in.

If you want a fast read on where your API's surrounding infrastructure stands — SSL configuration, security headers, CSP, cookie flags, DNS, and CORS — run a free scan at websentry.dev and get an A–F grade with specifics on what to fix first.

Check your own site

Run a free security scan and see if your site has the issues covered in this article. Results in under 30 seconds.