How do you secure a GraphQL API against malicious queries?
Protect GraphQL from DoS with depth limiting, query complexity analysis, pagination caps, rate limiting, persisted queries, and disabled introspection.
Expected Interview Answer
You secure a GraphQL API against malicious queries by layering defenses that cap how expensive any single query can be and who is allowed to run it: query depth limiting, complexity/cost analysis, pagination limits, timeouts, rate limiting, persisted queries, and disabling introspection in production.
GraphQL's flexibility lets a client request deeply nested or highly connected data in one call, so a single crafted query can exhaust CPU, memory, or database connections. The core mitigations bound cost before execution: reject queries past a maximum depth, assign each field a cost and reject over a budget, force paginated lists to use `first`/`last` caps, and enforce per-operation timeouts. Around that, add authentication and field-level authorization, rate limit by client, allowlist operations with persisted queries, and turn off introspection and verbose errors so attackers cannot map the schema.
- Prevents denial-of-service from expensive nested queries
- Bounds server cost before execution begins
- Limits schema discovery by disabling introspection
- Enforces per-field authorization, not just per-endpoint
- Reduces abuse through rate limiting and persisted queries
AI Mentor Explanation
Securing a GraphQL API is like a captain setting field restrictions and over limits so no single passage of play can run away with the match. Fielding rules cap how many can stand outside the circle, the over count bounds the innings, and the umpire stops dangerous deliveries, just as depth limits, cost budgets, and timeouts bound how much any one query can demand.
Step-by-Step Explanation
Step 1
Limit query depth
Reject any operation whose nesting exceeds a maximum (e.g. 7 levels) to stop recursive, cyclic explosions.
Step 2
Apply cost/complexity analysis
Assign each field a cost, multiply by pagination arguments, and reject queries over a per-request budget before execution.
Step 3
Cap pagination and timeouts
Require `first`/`last` with sane maximums and enforce a per-operation execution timeout.
Step 4
Authenticate and authorize per field
Check identity, then authorize at the field/resolver level rather than trusting the endpoint alone.
Step 5
Rate limit and use persisted queries
Throttle by client and allowlist known operations so only vetted queries reach the server.
Step 6
Harden production config
Disable introspection and mask internal error details so attackers cannot map or fingerprint the schema.
What Interviewer Expects
- Awareness that GraphQL flexibility enables DoS via nested queries
- Naming depth limiting and complexity/cost analysis
- Mention of pagination caps and timeouts
- Field-level authorization, not just authentication
- Persisted queries and disabling introspection in production
Common Mistakes
- Assuming REST-style endpoint auth is enough for GraphQL
- Relying only on depth limiting without cost analysis
- Leaving introspection enabled in production
- Returning verbose errors that leak schema internals
- Forgetting to cap list pagination, allowing huge fan-out
Best Answer (HR Friendly)
“GraphQL lets clients ask for a lot in one request, so a bad actor could send a huge, expensive query to crash the server. You protect against this by limiting how deep and complex a query can be, capping list sizes, adding timeouts and rate limits, checking permissions, and hiding the schema in production.”
Code Example
import depthLimit from 'graphql-depth-limit'
import { ApolloServer } from '@apollo/server'
const server = new ApolloServer({
schema,
validationRules: [depthLimit(7)],
introspection: process.env.NODE_ENV !== 'production',
})import { createComplexityRule, simpleEstimator } from 'graphql-query-complexity'
const complexityRule = createComplexityRule({
maximumComplexity: 1000,
estimators: [simpleEstimator({ defaultComplexity: 1 })],
onComplete: (complexity) => {
if (complexity > 1000) {
throw new Error(`Query too expensive: ${complexity}`)
}
},
})
// add complexityRule to validationRules alongside depthLimit(7)Follow-up Questions
- How does query complexity analysis differ from depth limiting?
- What are persisted (allowlisted) queries and why do they help?
- Why should introspection be disabled in production?
- How do you implement field-level authorization in resolvers?
- How do batching attacks with aliases threaten a GraphQL API?
MCQ Practice
1. Why is GraphQL uniquely vulnerable to expensive-query DoS?
A single GraphQL query can nest and fan out arbitrarily, making one request very expensive to resolve.
2. Which measure best prevents an attacker from mapping your schema?
Disabling introspection prevents attackers from querying the schema definition to plan attacks.
3. What does query complexity analysis do?
Complexity analysis scores a query's total cost and rejects it if it exceeds the allowed budget.
Flash Cards
Main GraphQL DoS risk? — Deeply nested or highly connected queries that exhaust CPU, memory, or database connections.
Depth limiting? — Rejecting any query whose nesting exceeds a configured maximum level.
Complexity analysis? — Assigning per-field costs and rejecting queries whose total exceeds a budget before execution.
Why disable introspection in prod? — It stops attackers from downloading the full schema to plan targeted queries.
Persisted queries? — An allowlist of known operations so only vetted queries are accepted by the server.
Continue Learning
Related Interview Questions
What is query complexity analysis and depth limiting in GraphQL?
medium
How do you handle authentication and authorization in a GraphQL API?
hard
What is a GraphQL schema and what is the Schema Definition Language (SDL)?
medium
How do you implement field-level authorisation in GraphQL without scattering checks through every resolver?
hard