Bird
Raised Fist0
GraphQLquery~10 mins

GraphQL security best practices

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Introduction

GraphQL lets you ask for exactly the data you want. But without care, it can let bad users see or change data they shouldn't. Security best practices help keep your data safe.

When building a website or app that uses GraphQL to get or change data
When you want to stop people from asking too many questions at once and slowing your system
When you want to make sure only the right people can see or change certain data
When you want to protect your server from attacks that try to confuse or overload it
When you want to keep user data private and safe from hackers
Syntax
GraphQL
type Query {
  # Define what data can be read
  user(id: ID!): User
}

type Mutation {
  # Define what data can be changed
  updateUser(id: ID!, name: String): User
}

# Example of adding security checks in resolver functions
const resolvers = {
  Query: {
    user: (parent, args, context) => {
      if (!context.user) throw new Error('Not authenticated');
      // Check if user has permission
      if (!context.user.canViewUser(args.id)) throw new Error('Not authorized');
      return getUserById(args.id);
    }
  },
  Mutation: {
    updateUser: (parent, args, context) => {
      if (!context.user) throw new Error('Not authenticated');
      if (!context.user.canEditUser(args.id)) throw new Error('Not authorized');
      return updateUser(args.id, args.name);
    }
  }
};

Resolvers are where you check who is asking and what they can do.

Always check authentication (who you are) and authorization (what you can do).

Examples
This example stops anyone who is not logged in from getting user data.
GraphQL
# Example 1: Simple authentication check in resolver
const resolvers = {
  Query: {
    user: (parent, args, context) => {
      if (!context.user) throw new Error('Not authenticated');
      return getUserById(args.id);
    }
  }
};
This example only lets admins update user info.
GraphQL
# Example 2: Authorization check for editing data
const resolvers = {
  Mutation: {
    updateUser: (parent, args, context) => {
      if (!context.user) throw new Error('Not authenticated');
      if (!context.user.isAdmin) throw new Error('Not authorized');
      return updateUser(args.id, args.name);
    }
  }
};
This stops queries that are too deep, which can slow down your server.
GraphQL
# Example 3: Limiting query depth to prevent complex queries
const depthLimit = require('graphql-depth-limit');
const server = new ApolloServer({
  schema,
  validationRules: [depthLimit(5)]
});
This limits how many queries a user can make in a minute to prevent overload.
GraphQL
# Example 4: Rate limiting to stop too many requests
const rateLimit = require('graphql-rate-limit');
const server = new ApolloServer({
  schema,
  validationRules: [rateLimit({ max: 100, window: '1m' })]
});
Sample Program

This program creates a simple GraphQL server. It checks if a user is logged in before giving data. It also checks if the user is an admin before allowing updates.

GraphQL
# Sample GraphQL server with basic security checks
const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
  type User {
    id: ID!
    name: String
  }

  type Query {
    user(id: ID!): User
  }

  type Mutation {
    updateUser(id: ID!, name: String): User
  }
`;

const users = [
  { id: '1', name: 'Alice' },
  { id: '2', name: 'Bob' }
];

const resolvers = {
  Query: {
    user: (parent, args, context) => {
      if (!context.user) throw new Error('Not authenticated');
      const user = users.find(u => u.id === args.id);
      if (!user) throw new Error('User not found');
      return user;
    }
  },
  Mutation: {
    updateUser: (parent, args, context) => {
      if (!context.user) throw new Error('Not authenticated');
      if (!context.user.isAdmin) throw new Error('Not authorized');
      const user = users.find(u => u.id === args.id);
      if (!user) throw new Error('User not found');
      user.name = args.name;
      return user;
    }
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: () => {
    // Simulate logged-in admin user
    return { user: { id: '99', isAdmin: true } };
  }
});

server.listen().then(({ url }) => {
  console.log(`Server ready at ${url}`);
});
OutputSuccess
Important Notes

Always check both authentication and authorization in your resolvers.

Use query depth limiting and rate limiting to protect your server from heavy or malicious queries.

Common mistake: trusting client input without checks can expose sensitive data.

Summary

GraphQL security means controlling who can see or change data.

Use authentication and authorization checks in your resolver functions.

Protect your server with limits on query complexity and request rates.

Practice

(1/5)
1. What is the main purpose of authentication in GraphQL security?
easy
A. To encrypt the data sent between client and server
B. To limit the number of queries a user can make
C. To verify the identity of the user making the request
D. To format the GraphQL schema correctly

Solution

  1. Step 1: Understand authentication role

    Authentication checks who the user is before allowing access.
  2. Step 2: Differentiate from other security measures

    Limiting queries and encryption are different security aspects, not authentication.
  3. Final Answer:

    To verify the identity of the user making the request -> Option C
  4. Quick Check:

    Authentication = Verify user identity [OK]
Hint: Authentication means checking who you are [OK]
Common Mistakes:
  • Confusing authentication with authorization
  • Thinking authentication limits query size
  • Mixing authentication with encryption
2. Which of the following is the correct way to limit query complexity in a GraphQL server?
easy
A. Allow unlimited queries and rely on client honesty
B. Use SQL injection to filter queries
C. Disable authentication to speed up queries
D. Use a middleware that calculates query depth and rejects too deep queries

Solution

  1. Step 1: Identify query complexity control

    Middleware can analyze query depth and reject overly complex queries to protect the server.
  2. Step 2: Eliminate incorrect options

    Allowing unlimited queries or disabling authentication weakens security; SQL injection is an attack, not a defense.
  3. Final Answer:

    Use a middleware that calculates query depth and rejects too deep queries -> Option D
  4. Quick Check:

    Limit query complexity = Middleware checks depth [OK]
Hint: Middleware can block too complex queries [OK]
Common Mistakes:
  • Ignoring query complexity limits
  • Confusing SQL injection with security measure
  • Disabling authentication to improve speed
3. Given this GraphQL resolver snippet, what will happen if a user without proper role tries to access the data?
const resolver = (parent, args, context) => {
  if (!context.user.roles.includes('admin')) {
    throw new Error('Access denied');
  }
  return getData();
};
medium
A. An error 'Access denied' will be thrown for non-admin users
B. The data will be returned regardless of user role
C. The server will crash due to missing roles
D. The resolver will ignore the role check and return null

Solution

  1. Step 1: Analyze role check in resolver

    The code checks if the user roles include 'admin'. If not, it throws an error.
  2. Step 2: Understand error handling

    Throwing an error stops execution and returns 'Access denied' to the client.
  3. Final Answer:

    An error 'Access denied' will be thrown for non-admin users -> Option A
  4. Quick Check:

    Role check fails = Error thrown [OK]
Hint: Throw error if user lacks role [OK]
Common Mistakes:
  • Assuming data returns without role check
  • Thinking server crashes on missing role
  • Believing null is returned instead of error
4. Identify the security issue in this GraphQL server setup:
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => ({ user: req.user })
});

// No rate limiting or query complexity checks applied
medium
A. Missing authentication in context setup
B. No rate limiting or query complexity protection
C. Resolvers are not defined
D. Using ApolloServer is insecure

Solution

  1. Step 1: Review context and security features

    Context passes user info, so authentication may exist, but no rate limiting or complexity checks are shown.
  2. Step 2: Identify missing protections

    Without rate limiting and query complexity checks, server is vulnerable to overload and abuse.
  3. Final Answer:

    No rate limiting or query complexity protection -> Option B
  4. Quick Check:

    Missing limits = Vulnerable server [OK]
Hint: Always add rate limits and complexity checks [OK]
Common Mistakes:
  • Assuming ApolloServer is insecure by default
  • Confusing missing resolvers with security issue
  • Ignoring rate limiting importance
5. You want to protect your GraphQL API from abuse by limiting both query complexity and request rate. Which combination of methods is best practice?
hard
A. Implement query depth analysis middleware and use a rate limiter like Redis to track requests
B. Only use authentication tokens without any query or rate limits
C. Disable introspection to prevent all queries
D. Allow unlimited queries but log all requests for later review

Solution

  1. Step 1: Understand query complexity protection

    Middleware that analyzes query depth helps prevent expensive queries that overload the server.
  2. Step 2: Understand rate limiting

    Using a rate limiter like Redis tracks and limits how many requests a user can make in a time window.
  3. Step 3: Evaluate other options

    Authentication alone doesn't limit abuse; disabling introspection breaks development; logging alone doesn't prevent abuse.
  4. Final Answer:

    Implement query depth analysis middleware and use a rate limiter like Redis to track requests -> Option A
  5. Quick Check:

    Combine depth check + rate limiter = Best protection [OK]
Hint: Use middleware plus rate limiter for best security [OK]
Common Mistakes:
  • Relying only on authentication
  • Disabling introspection breaks tools
  • Logging without limiting requests