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
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.