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.
GraphQL security best practices
Start learning this pattern below
Jump into concepts and practice - no test required
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).
# 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); } } };
# 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); } } };
# Example 3: Limiting query depth to prevent complex queries const depthLimit = require('graphql-depth-limit'); const server = new ApolloServer({ schema, validationRules: [depthLimit(5)] });
# 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' })] });
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.
# 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}`); });
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.
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
Solution
Step 1: Understand authentication role
Authentication checks who the user is before allowing access.Step 2: Differentiate from other security measures
Limiting queries and encryption are different security aspects, not authentication.Final Answer:
To verify the identity of the user making the request -> Option CQuick Check:
Authentication = Verify user identity [OK]
- Confusing authentication with authorization
- Thinking authentication limits query size
- Mixing authentication with encryption
Solution
Step 1: Identify query complexity control
Middleware can analyze query depth and reject overly complex queries to protect the server.Step 2: Eliminate incorrect options
Allowing unlimited queries or disabling authentication weakens security; SQL injection is an attack, not a defense.Final Answer:
Use a middleware that calculates query depth and rejects too deep queries -> Option DQuick Check:
Limit query complexity = Middleware checks depth [OK]
- Ignoring query complexity limits
- Confusing SQL injection with security measure
- Disabling authentication to improve speed
const resolver = (parent, args, context) => {
if (!context.user.roles.includes('admin')) {
throw new Error('Access denied');
}
return getData();
};Solution
Step 1: Analyze role check in resolver
The code checks if the user roles include 'admin'. If not, it throws an error.Step 2: Understand error handling
Throwing an error stops execution and returns 'Access denied' to the client.Final Answer:
An error 'Access denied' will be thrown for non-admin users -> Option AQuick Check:
Role check fails = Error thrown [OK]
- Assuming data returns without role check
- Thinking server crashes on missing role
- Believing null is returned instead of error
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({ user: req.user })
});
// No rate limiting or query complexity checks appliedSolution
Step 1: Review context and security features
Context passes user info, so authentication may exist, but no rate limiting or complexity checks are shown.Step 2: Identify missing protections
Without rate limiting and query complexity checks, server is vulnerable to overload and abuse.Final Answer:
No rate limiting or query complexity protection -> Option BQuick Check:
Missing limits = Vulnerable server [OK]
- Assuming ApolloServer is insecure by default
- Confusing missing resolvers with security issue
- Ignoring rate limiting importance
Solution
Step 1: Understand query complexity protection
Middleware that analyzes query depth helps prevent expensive queries that overload the server.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.Step 3: Evaluate other options
Authentication alone doesn't limit abuse; disabling introspection breaks development; logging alone doesn't prevent abuse.Final Answer:
Implement query depth analysis middleware and use a rate limiter like Redis to track requests -> Option AQuick Check:
Combine depth check + rate limiter = Best protection [OK]
- Relying only on authentication
- Disabling introspection breaks tools
- Logging without limiting requests
