Implementing GraphQL Security
1. Protecting Against Query Abuse
| Defense | Detail |
|---|---|
| Depth limit | Prevent recursive nesting attacks |
| Complexity limit | Cost-based budget |
| Rate limit | Per IP and per user |
| Pagination cap | Max first/last = 100 |
| Timeout | 30s hard cap |
2. Implementing Query Whitelisting
Example: Persisted-only mode
plugins: [usePersistedOperations({
getPersistedOperation: (hash) => allowlist.get(hash),
allowArbitraryOperations: false // reject anything not in allowlist
})]
3. Preventing Schema Leakage
| Practice | Detail |
|---|---|
| Disable introspection in prod | Defense in depth |
| Suggestion blocking | Disable "Did you mean…" hints |
| Mask error stacks | Strip in production formatter |
| Persisted queries | Best protection — schema-as-API hidden |
4. Validating Input Data
| Layer | Detail |
|---|---|
| Schema | Custom scalars (Email, URL) reject malformed early |
| Resolver | Zod / Joi for business rules |
| DB | Constraints as last line of defense |
5. Implementing CSRF Protection
Example: Apollo CSRF prevention
new ApolloServer({
csrfPrevention: true // requires apollo-require-preflight or non-simple Content-Type
});
| Defense | Detail |
|---|---|
| Preflight required | Force CORS preflight to validate origin |
| SameSite cookies | Lax/Strict prevents cross-site auth |
| Avoid GET mutations | Spec already forbids; enforce in router |
6. Securing WebSocket Connections
| Practice | Detail |
|---|---|
| wss:// only | TLS for all subscription traffic |
| Auth in connectionParams | Validate at connection_init |
| Origin check | Reject foreign origins |
| Heartbeat / ping | Detect dead connections |
| Per-connection rate limit | Cap subscriptions per session |
7. Implementing API Key Authentication
Example: API key middleware
context: async ({ req }) => {
const key = req.headers["x-api-key"];
if (!key) throw new GraphQLError("Missing API key", { extensions: { code: "UNAUTHENTICATED" } });
const client = await db.apiKey.findUnique({ where: { hash: sha256(key) } });
if (!client || client.revokedAt) throw new GraphQLError("Invalid key", { extensions: { code: "UNAUTHENTICATED" } });
return { client };
}
8. Using TLS/HTTPS
| Setting | Detail |
|---|---|
| TLS 1.2+ | Disable older protocols |
| HSTS | Strict-Transport-Security: max-age=31536000; includeSubDomains |
| HTTP → HTTPS redirect | Force upgrade |
| mTLS | Service-to-service inside mesh |
9. Auditing Security Vulnerabilities
| Tool | Detail |
|---|---|
| npm audit / pnpm audit | Dependency CVEs |
| Snyk / Dependabot | Automated PRs |
| Inql / GraphQL Cop | GraphQL-specific scanners |
| OWASP ZAP | Active scanning |
10. Following OWASP Guidelines
| OWASP API Top 10 Risk | GraphQL Mitigation |
|---|---|
| BOLA (auth) | Field-level authz; verify ownership |
| Broken authn | Strong JWT validation, key rotation |
| Excessive data exposure | Don't expose sensitive fields publicly |
| Resource exhaustion | Depth + complexity + rate limits |
| Mass assignment | Explicit input types only |
| Injection | Parameterized queries, never string-concat user input |
| Improper inventory | Schema registry; deprecate old fields |