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

  1. Broken Object Level Authorization
  2. Broken Authentication
  3. Excessive Data Exposure
  4. Resource Exhaustion
  5. Broken Function Level Authorization
  6. Mass Assignment
  7. Security Misconfiguration
  8. Injection
  9. Improper Assets Management
  10. 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.