GraphQL Security â Depth Limiting, Cost Analysis, and Protection
In this tutorial, you will learn about Graphql Security. We cover key concepts, practical examples, and best practices to help you master this topic.
GraphQL APIs face unique security challenges â a single malicious query can overload your server through deep nesting, expensive field requests, or introspection attacks.
What You'll Learn
You will learn how to protect GraphQL APIs with depth limiting, query cost analysis, Rate Limiting, introspection control, CSRF protection, and persistent query allowlists.
Why Security Matters
GraphQL's flexibility is also its vulnerability. A client can request deeply nested data (10+ levels), request expensive fields repeatedly, or introspect the entire schema to find weaknesses. Without protection, a single { threats { device { threats { device { ... } } } } } can crash your server. DodaTech's Durga Antivirus Pro implements three layers of GraphQL security â depth limits, cost analysis, and rate limiting â handling 50,000 requests/minute without incident.
flowchart TB
A["Incoming Query"] --> B{"Depth Limit\n(max 7 levels)"}
B -->|"Exceeded"| C["Reject: Query too deep"]
B -->|"OK"| D{"Cost Analysis\n(max 1000)"}
D -->|"Exceeded"| E["Reject: Query too expensive"]
D -->|"OK"| F{"Auth Check"}
F -->|"Unauthorized"| G["Reject: Login required"]
F -->|"OK"| H["Execute Query"]
style H fill:#bbf7d0,stroke:#16a34a
style C fill:#fca5a5,stroke:#dc2626
style E fill:#fca5a5,stroke:#dc2626
style G fill:#fca5a5,stroke:#dc2626
Prerequisites: Apollo Server configuration. Understanding of security concepts.
Query Depth Limiting
const depthLimit = require('graphql-depth-limit');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
depthLimit(7, { ignore: [ /^__/, /^internal/ ] }),
],
});
// Query depth 3 (safe)
query { threats { device { name } } }
// Query depth 15 (blocked)
query { threats { device { threats { device { threats { device { threats { ... } } } } } } } }
// â Error: Query depth limit of 7 exceeded
Query Cost Analysis
const { costAnalysis } = require('graphql-cost-analysis');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
costAnalysis({
maximumCost: 1000,
defaultCost: 1,
costMap: {
// Expensive fields cost more
"Device.threats": 10,
"User.devices": 5,
"Threat.rawAnalysis": 20,
"Scan.fullResults": 15,
},
onComplete(cost) {
console.log(`Query cost: ${cost}`);
},
}),
],
});
// Example cost calculation
// query { devices { threats { analysis } } }
// devices (1) + threats per device (10 per à N) + analysis (20 per)
// = 1 + (10 Ã N) + (20 Ã N Ã M)
Rate Limiting for GraphQL
const rateLimit = require('express-rate-limit');
const { ApolloServer } = require('apollo-server-express');
// IP-based rate limiting
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // 100 requests per minute
message: { errors: [{ message: 'Too many requests', extensions: { code: 'RATE_LIMITED' } }] },
});
// More granular: cost-based rate limiting
const costTracker = new Map();
function costBasedRateLimit(context, queryCost) {
const key = context.user?.id || context.ip;
const current = costTracker.get(key) || 0;
const newTotal = current + queryCost;
if (newTotal > 5000) { // Max 5000 cost per minute
throw new Error('Query budget exceeded');
}
costTracker.set(key, newTotal);
setTimeout(() => costTracker.set(key, (costTracker.get(key) || 0) - queryCost), 60000);
}
Introspection Control
const server = new ApolloServer({
typeDefs,
resolvers,
// Disable introspection in production
introspection: process.env.NODE_ENV !== 'production',
});
// Or: allow introspection only for authenticated admins
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true, // Still enabled
plugins: [{
requestDidStart({ request, context }) {
// Block introspection queries from non-admins
if (isIntrospectionQuery(request.query) && context.user?.role !== 'ADMIN') {
throw new Error('Introspection requires admin role');
}
},
}],
});
Persisted Query Allowlist
// Only allow registered queries (strict mode)
const allowlist = new Set([
'hash1abc...', // GetThreats
'hash2def...', // GetDevices
'hash3ghi...', // CreateDevice
]);
const server = new ApolloServer({
typeDefs,
resolvers,
persistedQueries: {
ttl: 900,
},
plugins: [{
requestDidStart({ request }) {
// Reject queries not in the allowlist
if (process.env.NODE_ENV === 'production') {
const hash = request.extensions?.persistedQuery?.sha256Hash;
if (!hash || !allowlist.has(hash)) {
throw new Error('Query not in allowlist');
}
}
},
}],
});
CSRF Protection
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true, // Requires apollo-require-preflight header
});
// Client must include this header on mutations:
// fetch('/graphql', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// 'apollo-require-preflight': 'true', // Required by csrfPrevention
// },
// body: JSON.stringify({ query: 'mutation { ... }' }),
// });
Security Headers
const helmet = require('helmet');
const { ApolloServer } = require('apollo-server-express');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
},
}));
Common Mistakes
1. Leaving Introspection Enabled in Production
Without introspection: false, anyone can query __schema to see all your types, fields, arguments, and deprecated fields â a roadmap for attackers.
2. No Depth Limits
Without depth limiting, a client creates an infinite loop: threats { device { threats { device { ... } } } }. Depth limit of 7-10 prevents this.
3. Ignoring Cost Analysis
Depth limits alone don't prevent expensive queries. A query at depth 3 that requests 1000 devices with expensive fields can still overload the server.
4. Not Implementing Rate Limiting
Without rate limiting, an attacker floods your API with requests. Implement both IP-based and cost-based rate limiting.
5. Using Default CORS Settings
cors({ origin: '*' }) allows any website to make requests to your GraphQL endpoint. Restrict to specific origins.
Practice Questions
- What does query depth limiting prevent?
- How does cost analysis differ from depth limiting?
- Why disable introspection in production?
- What is CSRF protection in Apollo Server?
- What are persisted query allowlists?
Answers:
- Depth limiting prevents deeply nested queries from consuming excessive server resources. A depth of 7-10 catches malicious or accidental infinite nesting.
- Depth limiting restricts nesting levels. Cost analysis assigns weights to fields and limits total query cost. Expensive but shallow queries pass depth limits but fail cost analysis.
- Introspection exposes every type, field, argument, and directive in your schema. Attackers use this information to find weak points and craft malicious queries.
- CSRF prevention requires a custom header (
apollo-require-preflight) on mutations. Browsers enforce CORS on custom headers, preventing cross-site request forgery attacks. - Persisted query allowlists restrict the API to a predefined set of queries. Only queries whose hashes are in the allowlist can execute. This is the most restrictive and secure mode.
Challenge: Implement a comprehensive security configuration for DodaTech's GraphQL API. Include: depth limit of 7 with graphql-depth-limit, cost analysis with graphql-cost-analysis (max 1000, with appropriate costs for expensive fields), rate limiting (100 req/min per IP, 5000 cost/min per user), CSRF prevention, disabled introspection in production, helmet security headers, CORS restricted to dashboard domains, and a persisted query allowlist for critical operations.
FAQ
Mini Project
Implement a security-hardened Apollo Server for DodaTech. Configure all security measures: depth limiting (7), cost analysis (max 1000), rate limiting (100 req/min, 5000 cost/min), CSRF prevention, disabled introspection in production, helmet security headers, CORS with origin whitelist, persisted query allowlist for 10 critical queries, and write security tests that verify each protection blocks attack queries.
What's Next
| Topic | Description |
|---|---|
| Performance | Query optimization and Caching |
| Project | Full-stack GraphQL application |
| Testing | Testing GraphQL APIs |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro