乐闻世界logo
搜索文章和话题

What are the best practices for GraphQL security

2月21日 17:01

GraphQL Security Best Practices

While GraphQL's flexibility is powerful, it also brings unique security challenges. Here are key security measures to protect GraphQL APIs.

1. Authentication and Authorization

Authentication Mechanism

javascript
const { ApolloServer } = require('apollo-server'); const jwt = require('jsonwebtoken'); const server = new ApolloServer({ typeDefs, resolvers, context: ({ req }) => { // Get token from request header const token = req.headers.authorization || ''; try { // Verify token const decoded = jwt.verify(token, process.env.JWT_SECRET); return { user: decoded }; } catch (error) { return { user: null }; } } });

Authorization Middleware

javascript
const { AuthenticationError, ForbiddenError } = require('apollo-server-express'); const resolvers = { Query: { me: (parent, args, context) => { if (!context.user) { throw new AuthenticationError('Not authenticated'); } return context.user; }, users: (parent, args, context) => { if (!context.user || context.user.role !== 'ADMIN') { throw new ForbiddenError('Admin permission required'); } return User.findAll(); } } };

2. Query Depth Limiting

Prevent Deep Nesting Attacks

javascript
const depthLimit = require('graphql-depth-limit'); const server = new ApolloServer({ typeDefs, resolvers, validationRules: [ depthLimit(7, { ignore: ['ignoredField'] // Ignore specific fields }) ] });

Why needed:

  • Prevent malicious clients from sending deeply nested queries
  • Avoid server resource exhaustion
  • Prevent DoS attacks

3. Query Complexity Limiting

Limit Query Complexity

javascript
const { createComplexityLimitRule } = require('graphql-validation-complexity'); const complexityLimitRule = createComplexityLimitRule(1000, { onCost: (cost) => console.log(`Query complexity: ${cost}`), createError: (max, actual) => { return new Error(`Query complexity ${actual} exceeds maximum limit ${max}`); } }); const server = new ApolloServer({ typeDefs, resolvers, validationRules: [complexityLimitRule] });

Custom Complexity Calculation

javascript
const typeDefs = ` type Query { user(id: ID!): User @cost(complexity: 1) users(limit: Int!): [User!]! @cost(complexity: 5, multipliers: ["limit"]) posts(first: Int!): [Post!]! @cost(complexity: 10, multipliers: ["first"]) } `;

4. Rate Limiting

Using express-rate-limit

javascript
const rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests message: 'Too many requests, please try again later' }); app.use('/graphql', limiter);

GraphQL-specific Rate Limiting

javascript
const { GraphQLComplexityLimit } = require('graphql-complexity-limit'); const server = new ApolloServer({ typeDefs, resolvers, validationRules: [ new GraphQLComplexityLimit({ maxComplexity: 1000, onExceed: (complexity) => { throw new Error(`Query complexity ${complexity} exceeds limit`); } }) ] });

5. Input Validation

Using Yup Validation

javascript
const yup = require('yup'); const createUserSchema = yup.object().shape({ name: yup.string().required().min(2).max(100), email: yup.string().email().required(), age: yup.number().min(0).max(150).optional() }); const resolvers = { Mutation: { createUser: async (_, { input }) => { // Validate input await createUserSchema.validate(input); // Create user return User.create(input); } } };

GraphQL Schema Validation

graphql
input CreateUserInput { name: String! @constraint(minLength: 2, maxLength: 100) email: String! @constraint(format: "email") age: Int @constraint(min: 0, max: 150) }

6. Field-level Permission Control

Using Directives

graphql
directive @auth(requires: Role) on FIELD_DEFINITION enum Role { USER ADMIN } type User { id: ID! name: String! email: String! @auth(requires: ADMIN) salary: Float @auth(requires: ADMIN) }
javascript
const resolvers = { User: { email: (user, args, context) => { if (context.user?.role !== 'ADMIN') { throw new ForbiddenError('No permission to access this field'); } return user.email; }, salary: (user, args, context) => { if (context.user?.role !== 'ADMIN') { throw new ForbiddenError('No permission to access this field'); } return user.salary; } } };

7. Prevent Query Injection

Parameterized Queries

javascript
// Bad practice - string concatenation const query = `SELECT * FROM users WHERE id = '${userId}'`; // Good practice - parameterized queries const query = 'SELECT * FROM users WHERE id = ?'; const result = await db.query(query, [userId]);

Using ORM

javascript
// Using Sequelize ORM const user = await User.findOne({ where: { id: userId } });

8. CORS Configuration

javascript
const server = new ApolloServer({ typeDefs, resolvers, cors: { origin: ['https://yourdomain.com'], // Only allow specific domains credentials: true, methods: ['POST'] } });

9. Error Handling

Don't Expose Sensitive Information

javascript
const server = new ApolloServer({ typeDefs, resolvers, formatError: (error) => { // Don't expose detailed error information in production if (process.env.NODE_ENV === 'production') { return new Error('Internal server error'); } // Return full error information in development return error; } });

Custom Error Types

graphql
type Error { code: String! message: String! field: String } type UserResult { user: User errors: [Error!]! } type Mutation { createUser(input: CreateUserInput!): UserResult! }

10. Logging and Monitoring

Log Query Logs

javascript
const server = new ApolloServer({ typeDefs, resolvers, plugins: [ { requestDidStart: () => ({ didResolveOperation: (context) => { console.log('Query:', context.request.operationName); console.log('Variables:', JSON.stringify(context.request.variables)); } }) } ] });

Monitor Anomalous Queries

javascript
const server = new ApolloServer({ typeDefs, resolvers, plugins: [ { requestDidStart: () => ({ didEncounterErrors: (context) => { context.errors.forEach(error => { console.error('Query error:', error.message); // Send to monitoring system monitoringService.logError(error); }); } }) } ] });

11. Query Whitelist

Using Persisted Queries

javascript
const { createPersistedQueryLink } = require('@apollo/client/link/persisted-queries'); const { sha256 } = require('crypto-hash'); const link = createPersistedQueryLink({ sha256, useGETForHashedQueries: true }); // Only allow predefined queries const allowedQueries = new Set([ 'hash1', 'hash2' ]); const server = new ApolloServer({ typeDefs, resolvers, persistedQueries: { cache: new Map(), ttl: 3600 } });

12. Security Checklist

  • Implement authentication mechanism (JWT, OAuth)
  • Implement authorization mechanism (role-based access control)
  • Limit query depth
  • Limit query complexity
  • Implement rate limiting
  • Validate all inputs
  • Implement field-level permission control
  • Prevent query injection
  • Configure CORS
  • Handle errors properly
  • Log query logs
  • Monitor anomalous queries
  • Use query whitelist
  • Regular security audits
  • Keep dependencies updated

13. Common Security Threats and Protections

ThreatDescriptionProtection
Deep nesting attackMalicious clients send deeply nested queriesLimit query depth
Complexity attackSend high-complexity queries to consume resourcesLimit query complexity
DoS attackLarge number of requests make service unavailableRate limiting, query whitelist
Injection attackMalicious input causes SQL injectionParameterized queries, input validation
Unauthorized accessAccess unauthorized dataAuthentication, authorization, field-level permission control
Information leakageError messages expose sensitive dataProper error handling
标签:GraphQL