Security best practices in GraphQL
From the GraphQL cheat sheet ยท Best Practices ยท verified Jul 2026
Security best practices
javascript
// Security configuration
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@as-integrations/express4');
const { GraphQLError } = require('graphql');
const rateLimit = require('express-rate-limit');
const depthLimit = require('graphql-depth-limit');
const costAnalysis = require('graphql-cost-analysis');
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Max 100 requests
message: 'Too many requests'
});
app.use('/graphql', limiter);
// Apollo Server with security
const server = new ApolloServer({
typeDefs,
resolvers,
// Disable introspection in production
introspection: process.env.NODE_ENV !== 'production',
// Validation rules
validationRules: [
// Limit query depth
depthLimit(7),
// Limit query complexity
costAnalysis({
maximumCost: 1000,
defaultCost: 1,
variables: {},
scalarCost: 1,
objectCost: 2,
listFactor: 10,
introspectionCost: 1000,
enforceIntrospectionCost: true,
onComplete: (cost) => {
console.log('Query cost:', cost);
}
})
],
// Format errors (hide sensitive info)
formatError: (err) => {
// Log full error server-side
console.error(err);
// Don't leak internal errors
if (err.message.includes('Database')) {
return new Error('Internal server error');
}
return err;
},
});
// Context moves to the integration (Apollo Server 4+)
app.use('/graphql', cors(), express.json(), expressMiddleware(server, {
context: async ({ req }) => {
// CSRF protection
const csrfToken = req.headers['x-csrf-token'];
if (req.method === 'POST' && !csrfToken) {
throw new Error('CSRF token missing');
}
// Rate limiting per user
const ip = req.ip;
const userLimiter = getUserRateLimiter(ip);
if (!userLimiter.check()) {
throw new Error('Rate limit exceeded');
}
return { req };
},
}));
// Input validation
const validator = require('validator');
const resolvers = {
Mutation: {
createUser: async (parent, { input }) => {
// Validate email
if (!validator.isEmail(input.email)) {
throw new GraphQLError('Invalid email', { extensions: { code: 'BAD_USER_INPUT' } });
}
// Validate and sanitize
const sanitized = {
name: validator.escape(input.name),
email: validator.normalizeEmail(input.email),
bio: validator.escape(input.bio || '')
};
// Check for SQL injection attempts
if (containsSQLInjection(input)) {
throw new Error('Invalid input');
}
return createUser(sanitized);
}
}
};
// Timeout protection
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
{
requestDidStart() {
return {
willSendResponse(requestContext) {
// Timeout after 30 seconds
setTimeout(() => {
if (!requestContext.response.http.body) {
throw new Error('Request timeout');
}
}, 30000);
}
};
}
}
]
});๐ Always validate and sanitize inputs
โก Implement rate limiting
๐ Disable introspection in production
๐ฅ Use query depth and complexity limits