From latestaiagents
Comprehensive API security for REST and GraphQL APIs. Use this skill when building or reviewing API endpoints, implementing authentication, or securing data transfer. Activate when: API security, REST security, GraphQL security, API authentication, API rate limiting, API versioning, secure endpoint, API design.
npx claudepluginhub latestaiagents/agent-skills --plugin skills-authoringThis skill uses the workspace's default tool permissions.
**Secure your REST and GraphQL APIs against common attacks and vulnerabilities.**
Implements secure API design patterns including authentication, authorization, input validation, rate limiting, and protection against common vulnerabilities for REST, GraphQL, and WebSocket APIs. Use when designing, securing, or reviewing APIs.
Designs secure scalable APIs with REST, GraphQL, gRPC; OpenAPI 3.1 specs, OAuth2/JWT auth, rate limiting, pagination, OWASP Top 10 protections. Use for API architecture, gateways, endpoint security.
Provides API security guidance on authentication methods, rate limiting, input validation, CORS, security headers, and OWASP API Top 10 mitigations. Use for designing auth, implementing limits, or reviewing APIs.
Share bugs, ideas, or general feedback.
Secure your REST and GraphQL APIs against common attacks and vulnerabilities.
| Area | Controls |
|---|---|
| Authentication | OAuth 2.0, API keys, JWT |
| Authorization | Scopes, RBAC, resource ownership |
| Input Validation | Schema validation, type checking |
| Rate Limiting | Per-user, per-endpoint limits |
| Transport | HTTPS only, certificate pinning |
| Output | No sensitive data leakage |
// API key middleware
function apiKeyAuth(req, res, next) {
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({ error: 'API key required' });
}
// Constant-time comparison to prevent timing attacks
const validKey = await getApiKey(apiKey);
if (!validKey || !crypto.timingSafeEqual(
Buffer.from(apiKey),
Buffer.from(validKey.key)
)) {
return res.status(401).json({ error: 'Invalid API key' });
}
req.apiClient = validKey.client;
next();
}
// API key generation
function generateApiKey() {
const prefix = 'sk_live_'; // Identifiable prefix
const key = crypto.randomBytes(32).toString('hex');
return prefix + key;
}
// Store hashed keys
async function createApiKey(clientId) {
const key = generateApiKey();
const hash = crypto.createHash('sha256').update(key).digest('hex');
await db.apiKeys.create({
clientId,
keyHash: hash,
keyPrefix: key.substring(0, 12), // For identification
createdAt: new Date()
});
return key; // Only returned once
}
// OAuth 2.0 token endpoint
app.post('/oauth/token', async (req, res) => {
const { grant_type, client_id, client_secret, code, refresh_token } = req.body;
// Validate client
const client = await validateClient(client_id, client_secret);
if (!client) {
return res.status(401).json({ error: 'invalid_client' });
}
switch (grant_type) {
case 'authorization_code':
return handleAuthorizationCode(req, res, client, code);
case 'refresh_token':
return handleRefreshToken(req, res, client, refresh_token);
case 'client_credentials':
return handleClientCredentials(req, res, client);
default:
return res.status(400).json({ error: 'unsupported_grant_type' });
}
});
// Token response
function generateTokenResponse(user, client, scopes) {
const accessToken = jwt.sign(
{ sub: user.id, client_id: client.id, scopes },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
const refreshToken = crypto.randomBytes(64).toString('hex');
return {
access_token: accessToken,
token_type: 'Bearer',
expires_in: 900,
refresh_token: refreshToken,
scope: scopes.join(' ')
};
}
const Joi = require('joi');
// Define schemas
const schemas = {
createUser: Joi.object({
email: Joi.string().email().required(),
name: Joi.string().min(1).max(100).required(),
age: Joi.number().integer().min(0).max(150)
}),
updateUser: Joi.object({
name: Joi.string().min(1).max(100),
age: Joi.number().integer().min(0).max(150)
}).min(1) // At least one field required
};
// Validation middleware
function validate(schemaName) {
return (req, res, next) => {
const schema = schemas[schemaName];
const { error, value } = schema.validate(req.body, {
abortEarly: false,
stripUnknown: true // Remove unknown fields
});
if (error) {
return res.status(400).json({
error: 'Validation failed',
details: error.details.map(d => ({
field: d.path.join('.'),
message: d.message
}))
});
}
req.validatedBody = value;
next();
};
}
// Usage
app.post('/users', validate('createUser'), createUserHandler);
const { ApolloServer } = require('@apollo/server');
const depthLimit = require('graphql-depth-limit');
const { createComplexityLimitRule } = require('graphql-validation-complexity');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
// Prevent deep queries
depthLimit(5),
// Prevent complex queries
createComplexityLimitRule(1000)
],
plugins: [
// Rate limiting plugin
{
async requestDidStart() {
return {
async didResolveOperation(ctx) {
await checkRateLimit(ctx.request);
}
};
}
}
]
});
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
// Different limits for different endpoints
const rateLimits = {
// Standard API endpoints
standard: rateLimit({
store: new RedisStore({ client: redis }),
windowMs: 60 * 1000, // 1 minute
max: 100,
keyGenerator: (req) => req.apiClient?.id || req.ip,
standardHeaders: true,
legacyHeaders: false
}),
// Authentication endpoints (stricter)
auth: rateLimit({
store: new RedisStore({ client: redis }),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10,
keyGenerator: (req) => req.ip,
skipSuccessfulRequests: true // Only count failures
}),
// Expensive operations
expensive: rateLimit({
store: new RedisStore({ client: redis }),
windowMs: 60 * 60 * 1000, // 1 hour
max: 10,
keyGenerator: (req) => req.apiClient?.id || req.ip
})
};
// Apply to routes
app.use('/api/', rateLimits.standard);
app.use('/api/auth/', rateLimits.auth);
app.post('/api/export', rateLimits.expensive, exportHandler);
// Secure response headers
app.use((req, res, next) => {
// No caching for API responses
res.setHeader('Cache-Control', 'no-store');
res.setHeader('Pragma', 'no-cache');
// Prevent MIME sniffing
res.setHeader('X-Content-Type-Options', 'nosniff');
next();
});
// Filter sensitive fields from responses
function sanitizeResponse(data, allowedFields) {
if (Array.isArray(data)) {
return data.map(item => sanitizeResponse(item, allowedFields));
}
if (typeof data === 'object' && data !== null) {
return Object.keys(data)
.filter(key => allowedFields.includes(key))
.reduce((obj, key) => {
obj[key] = data[key];
return obj;
}, {});
}
return data;
}
// Usage
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
const safeUser = sanitizeResponse(user, ['id', 'name', 'email', 'createdAt']);
res.json(safeUser);
});
// Secure error handler
app.use((err, req, res, next) => {
// Log full error internally
logger.error('API Error', {
error: err.message,
stack: err.stack,
path: req.path,
method: req.method,
clientId: req.apiClient?.id
});
// Return safe error to client
const statusCode = err.statusCode || 500;
const response = {
error: {
code: err.code || 'INTERNAL_ERROR',
message: statusCode === 500
? 'An internal error occurred'
: err.message
}
};
// Include request ID for support
response.error.requestId = req.id;
res.status(statusCode).json(response);
});
/v1/, /v2/ for breaking changes