DEV Community

Cover image for GraphQL Security: Protecting Queries and Mutations

GraphQL Security: Protecting Queries and Mutations

GraphQL's flexibility is powerfulβ€”until attackers exploit it. Without proper defenses, queries can expose the entire schema, mutations can bypass validation, and resolvers can leak sensitive data. Securing GraphQL means defending at every layer: schema, query, and resolver.


1. Validate and Limit Query Complexity

Unrestricted queries invite query bombsβ€”attackers craft deeply nested queries that exhaust server resources.

import { buildSchema, graphql } from 'graphql';
import { createComplexityLimitMiddleware } from 'graphql-query-complexity';

const middleware = createComplexityLimitMiddleware({
  maximumComplexity: 1000,
  variables: {},
  onComplete: (complexity) => console.log(`Query complexity: ${complexity}`),
  createError: (max, actual) => new Error(`Query too complex: ${actual} > ${max}`)
});

app.use('/graphql', middleware);
Enter fullscreen mode Exit fullscreen mode

2. Authenticate and Authorize Mutations

Every mutation is a write operation. Enforce role-based access and validate permissions before executing.

const resolvers = {
  Mutation: {
    updateUser: (parent, args, context) => {
      if (!context.user) throw new Error('Unauthorized');
      if (context.user.id !== args.userId && context.user.role !== 'ADMIN') {
        throw new Error('Forbidden: Cannot modify other users');
      }
      return db.users.update(args.userId, args.data);
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

3. Hide Sensitive Fields from Schema

Introspection leaks your entire schema. Disable it in production and control field visibility per role.

const apollo = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: process.env.NODE_ENV !== 'production',
  context: ({ req }) => ({
    user: verifyToken(req.headers.authorization),
    isIntrospectionAllowed: process.env.NODE_ENV === 'development'
  })
});
Enter fullscreen mode Exit fullscreen mode

4. Sanitize Resolver Arguments

User input in mutations should be treated as hostile until proven otherwise.

import validator from 'validator';

const resolvers = {
  Mutation: {
    createPost: (parent, args) => {
      const sanitized = {
        title: validator.escape(args.title),
        content: validator.trim(args.content),
        tags: args.tags.map(t => validator.escape(t))
      };
      return db.posts.create(sanitized);
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

5. Rate Limit Per User

Prevent mutations from being used as attack vectors by enforcing per-user rate limits.

import rateLimit from 'graphql-rate-limit';

const rateLimitDirective = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 10
});

const typeDefs = `
  type Mutation {
    createPost(title: String!): Post @rateLimit(window: "15m", limit: 10)
  }
`;
Enter fullscreen mode Exit fullscreen mode

GraphQL's power comes from its precision. That same precision must apply to security.

With proper validation, complexity limits, and role-based access, GraphQL becomes a secure foundation for modern APIs.


Thanks for reading! If this post helped you understand GraphQL security patterns, please share it with your team or leave a comment with your own security wins.

I help teams implement GraphQL architectures that balance flexibility with bulletproof security.

Explore more: ILLAPEX

Top comments (0)