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

More GraphQL tasks

Back to the full GraphQL cheat sheet