GraphQL Security
GraphQL offers powerful flexibility but introduces unique attack vectors: query complexity attacks, introspection abuse, authorization bypass, and information disclosure.
Common Vulnerabilities in GraphQL
1. Query Depth / Complexity Attacks
Deeply nested queries can cause DoS by consuming excessive resources:
query MaliciousQuery {
user(id: "1") {
posts {
comments {
author {
posts {
comments {
author {
posts {
# ... infinitely nested
}
}
}
}
}
}
}
}
}
2. Introspection Abuse
Introspection enabled in production exposes the full schema, making reconnaissance easier:
query IntrospectionQuery {
__schema {
types {
name
fields {
name
type {
name
}
}
}
}
}
3. Broken Authorization
Authorization must happen in resolvers, not just in top-level queries. IDOR is common when authorization is not checked on nested fields.
4. Injection Attacks
GraphQL is not immune to SQLi or NoSQLi if inputs are not sanitized in resolvers.
Essential Protections
Query Depth Limiting
// Apollo Server example
import depthLimit from 'graphql-depth-limit';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(5)] // Max depth 5
});
Query Complexity Analysis
import { createComplexityLimitRule } from 'graphql-validation-complexity';
const server = new ApolloServer({
validationRules: [
createComplexityLimitRule(1000, {
scalarCost: 1,
objectCost: 5,
listFactor: 10
})
]
});
Rate Limiting
- Query-based: Limit queries per minute per user
- Complexity-based: Budget of complexity points
- Cost analysis: Assign a cost to each field
Authorization in GraphQL
Field-Level Authorization
const resolvers = {
Query: {
user: async (parent, { id }, context) => {
// Authorization at query level
if (!context.user) {
throw new AuthenticationError('Not authenticated');
}
return getUserById(id);
}
},
User: {
email: (user, args, context) => {
// Authorization at field level
if (context.user.id !== user.id && !context.user.isAdmin) {
return null; // Hide email from other users
}
return user.email;
},
ssn: (user, args, context) => {
// Extremely sensitive field
if (!context.user.isAdmin) {
throw new ForbiddenError('Admin only');
}
return user.ssn;
}
}
};
Directive-Based Authorization
type User @auth(requires: AUTHENTICATED) {
id: ID!
username: String!
email: String! @auth(requires: OWNER_OR_ADMIN)
ssn: String! @auth(requires: ADMIN)
}
Introspection Protection
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== 'production',
// Or control by role
plugins: [{
requestDidStart() {
return {
didResolveOperation({ request, context }) {
if (request.operationName === 'IntrospectionQuery'
&& !context.user?.isAdmin) {
throw new ForbiddenError('Introspection disabled');
}
}
}
}
}]
});
Input Validation
- Schema validation: GraphQL validates types automatically
- Custom scalars: Email, URL, DateTime with validation
- Input sanitization: Sanitize inputs before using them in resolvers
- Parameterized queries: Use prepared statements in the database
Batching and DataLoader
Prevent the N+1 problem, which can be exploited for DoS:
import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (userIds) => {
const users = await getUsersByIds(userIds);
return userIds.map(id => users.find(u => u.id === id));
});
const resolvers = {
Post: {
author: (post, args, { userLoader }) => {
return userLoader.load(post.authorId); // Batched!
}
}
};
Monitoring and Logging
- Query logging: Log all queries with metadata (user, IP, time)
- Error tracking: Sentry, Datadog for exceptions
- Performance monitoring: Apollo Studio, GraphQL Hive
- Anomaly detection: Alert on suspicious queries (overly complex, etc.)
Security Tools
- GraphQL Armor: Security middleware suite
- graphql-shield: Permissions layer with rules
- InQL: Burp Suite extension for GraphQL pentesting
- BatchQL: Security testing tool for GraphQL
- graphql-cop: Security auditing tool
OWASP GraphQL Top 10
- Broken Object Level Authorization
- Broken Authentication
- Excessive Data Exposure
- Resource Exhaustion
- Broken Function Level Authorization
- Mass Assignment
- Security Misconfiguration
- Injection
- Improper Assets Management
- Insufficient Logging & Monitoring
Secure Development Practices
- Implement authorization in ALL resolvers
- Disable introspection in production
- Limit query depth and complexity
- Aggressive rate limiting
- Use DataLoader to prevent N+1
- Sanitize inputs before processing
- Implement comprehensive logging
- Regular security audits and pentests
Final Recommendations
GraphQL requires a shift in security mindset compared to REST. Client flexibility demands robust defenses on the server. Implement depth limiting, complexity analysis, and field-level authorization from the start. Use monitoring tools to detect abuse patterns. Test regularly with specialized tools such as InQL and BatchQL.
