Secure GraphQL APIs - authentication, authorization, rate limiting, and validation
View on GitHubpluginagentmarketplace/custom-plugin-graphql
developer-roadmap
January 20, 2026
Select agents to install to:
npx add-skill https://github.com/pluginagentmarketplace/custom-plugin-graphql/blob/main/skills/graphql-security/SKILL.md -a claude-code --skill graphql-securityInstallation paths:
.claude/skills/graphql-security/# GraphQL Security Skill
> Protect your GraphQL APIs from attacks
## Overview
Learn essential security patterns for GraphQL: JWT authentication, role-based authorization, rate limiting, query complexity limits, and input validation.
---
## Security Checklist
| Check | Priority | Implementation |
|-------|----------|----------------|
| Authentication | Critical | JWT with refresh tokens |
| Authorization | Critical | Field-level with graphql-shield |
| Rate Limiting | Critical | Per-user/IP with Redis |
| Query Depth | High | graphql-depth-limit |
| Query Complexity | High | graphql-query-complexity |
| Introspection | High | Disable in production |
| Input Validation | High | Validate all inputs |
| Error Masking | Medium | Hide internal errors |
---
## Core Patterns
### 1. JWT Authentication
```typescript
import jwt from 'jsonwebtoken';
// Token creation
function createTokens(user) {
const accessToken = jwt.sign(
{ userId: user.id, roles: user.roles },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
// Context setup
const context = async ({ req }) => {
const token = req.headers.authorization?.replace('Bearer ', '');
let user = null;
if (token) {
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
user = await db.users.findById(payload.userId);
} catch (e) {
// Token invalid or expired
}
}
return { user };
};
// Login resolver
const resolvers = {
Mutation: {
login: async (_, { email, password }) => {
const user = await db.users.findByEmail(email);
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
throw new GraphQLError('Invalid credentials', {
extensions: { code: 'UNAUTHORIZED' }
});
}
return { ...createTokens(user), user };
},
},
};
```
#