Agent Skills Framework Extension
Backend API Security Patterns Skill
When to Use This Skill
Use this skill when implementing backend api security patterns patterns in your codebase.
How to Use This Skill
- Review the patterns and examples below
- Apply the relevant patterns to your implementation
- Follow the best practices outlined in this skill
API security hardening, authentication patterns, and vulnerability prevention.
Core Capabilities
- Authentication - JWT, OAuth2, API keys
- Authorization - RBAC, ABAC, permissions
- Input Validation - Schema validation, sanitization
- Rate Limiting - Request throttling, abuse prevention
- Security Headers - CORS, CSP, HSTS
API Security Middleware Stack
// src/security/middleware/index.ts
import { Express } from 'express';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
import cors from 'cors';
import { authMiddleware } from './auth';
import { rbacMiddleware } from './rbac';
import { inputValidation } from './validation';
import { auditLogger } from './audit';
import { securityHeaders } from './headers';
export function configureSecurityMiddleware(app: Express): void {
// 1. Security headers (first layer)
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
}));
// 2. CORS configuration
app.use(cors({
origin: (origin, callback) => {
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
exposedHeaders: ['X-Request-ID', 'X-RateLimit-Remaining'],
maxAge: 86400,
}));
// 3. Rate limiting
app.use(rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per window
standardHeaders: true,
legacyHeaders: false,
handler: (req, res) => {
res.status(429).json({
error: {
code: 'RATE_LIMIT_EXCEEDED',
message: 'Too many requests, please try again later',
retryAfter: res.getHeader('Retry-After'),
},
});
},
}));
// 4. Request ID and audit logging
app.use(auditLogger);
// 5. Authentication (applies to protected routes)
app.use('/api', authMiddleware);
// 6. Input validation
app.use(inputValidation);
}
JWT Authentication
// src/security/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
interface JWTPayload {
sub: string;
email: string;
roles: string[];
permissions: string[];
iat: number;
exp: number;
jti: string;
}
interface AuthenticatedRequest extends Request {
user?: JWTPayload;
}
const publicPaths = [
'/api/health',
'/api/auth/login',
'/api/auth/register',
'/api/auth/refresh',
];
export async function authMiddleware(
req: AuthenticatedRequest,
res: Response,
next: NextFunction
): Promise<void> {
// Skip authentication for public paths
if (publicPaths.some(path => req.path.startsWith(path))) {
return next();
}
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
res.status(401).json({
error: {
code: 'MISSING_TOKEN',
message: 'Authorization header with Bearer token required',
},
});
return;
}
const token = authHeader.slice(7);
try {
// Verify token
const secret = process.env.JWT_SECRET!;
const payload = jwt.verify(token, secret, {
algorithms: ['HS256'],
issuer: process.env.JWT_ISSUER,
audience: process.env.JWT_AUDIENCE,
}) as JWTPayload;
// Check if token is blacklisted (for logout/revocation)
const isBlacklisted = await checkTokenBlacklist(payload.jti);
if (isBlacklisted) {
res.status(401).json({
error: {
code: 'TOKEN_REVOKED',
message: 'Token has been revoked',
},
});
return;
}
// Attach user to request
req.user = payload;
next();
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
res.status(401).json({
error: {
code: 'TOKEN_EXPIRED',
message: 'Token has expired',
},
});
} else if (error instanceof jwt.JsonWebTokenError) {
res.status(401).json({
error: {
code: 'INVALID_TOKEN',
message: 'Token is invalid',
},
});
} else {
res.status(500).json({
error: {
code: 'AUTH_ERROR',
message: 'Authentication failed',
},
});
}
}
}
async function checkTokenBlacklist(jti: string): Promise<boolean> {
// Check Redis for blacklisted token
const redis = getRedisClient();
const exists = await redis.exists(`blacklist:${jti}`);
return exists === 1;
}
// Token generation
export function generateTokens(user: { id: string; email: string; roles: string[] }): {
accessToken: string;
refreshToken: string;
expiresIn: number;
} {
const jti = crypto.randomUUID();
const accessToken = jwt.sign(
{
sub: user.id,
email: user.email,
roles: user.roles,
permissions: getRolePermissions(user.roles),
jti,
},
process.env.JWT_SECRET!,
{
algorithm: 'HS256',
expiresIn: '15m',
issuer: process.env.JWT_ISSUER,
audience: process.env.JWT_AUDIENCE,
}
);
const refreshToken = jwt.sign(
{ sub: user.id, jti: crypto.randomUUID(), type: 'refresh' },
process.env.JWT_REFRESH_SECRET!,
{ algorithm: 'HS256', expiresIn: '7d' }
);
return {
accessToken,
refreshToken,
expiresIn: 900, // 15 minutes in seconds
};
}
Role-Based Access Control
// src/security/middleware/rbac.ts
import { Request, Response, NextFunction } from 'express';
interface Permission {
resource: string;
action: 'create' | 'read' | 'update' | 'delete' | 'admin';
}
interface RoleDefinition {
name: string;
permissions: Permission[];
inherits?: string[];
}
const ROLES: Record<string, RoleDefinition> = {
admin: {
name: 'admin',
permissions: [
{ resource: '*', action: 'admin' },
],
},
manager: {
name: 'manager',
permissions: [
{ resource: 'users', action: 'read' },
{ resource: 'users', action: 'update' },
{ resource: 'projects', action: 'create' },
{ resource: 'projects', action: 'read' },
{ resource: 'projects', action: 'update' },
{ resource: 'projects', action: 'delete' },
],
inherits: ['user'],
},
user: {
name: 'user',
permissions: [
{ resource: 'profile', action: 'read' },
{ resource: 'profile', action: 'update' },
{ resource: 'projects', action: 'read' },
],
},
};
export function getRolePermissions(roles: string[]): string[] {
const permissions = new Set<string>();
function addRolePermissions(roleName: string): void {
const role = ROLES[roleName];
if (!role) return;
for (const perm of role.permissions) {
permissions.add(`${perm.resource}:${perm.action}`);
}
if (role.inherits) {
for (const inherited of role.inherits) {
addRolePermissions(inherited);
}
}
}
for (const role of roles) {
addRolePermissions(role);
}
return Array.from(permissions);
}
export function requirePermission(resource: string, action: string) {
return (req: AuthenticatedRequest, res: Response, next: NextFunction): void => {
const user = req.user;
if (!user) {
res.status(401).json({
error: { code: 'UNAUTHENTICATED', message: 'Authentication required' },
});
return;
}
const requiredPermission = `${resource}:${action}`;
const hasPermission =
user.permissions.includes(requiredPermission) ||
user.permissions.includes(`${resource}:admin`) ||
user.permissions.includes('*:admin');
if (!hasPermission) {
res.status(403).json({
error: {
code: 'FORBIDDEN',
message: `Permission denied: ${requiredPermission}`,
},
});
return;
}
next();
};
}
// Usage in routes
router.get('/users', requirePermission('users', 'read'), listUsers);
router.post('/users', requirePermission('users', 'create'), createUser);
router.delete('/users/:id', requirePermission('users', 'delete'), deleteUser);
Input Validation
// src/security/middleware/validation.ts
import { z } from 'zod';
import { Request, Response, NextFunction } from 'express';
import xss from 'xss';
// Sanitization helpers
export function sanitizeInput(input: string): string {
// Remove XSS vectors
let sanitized = xss(input);
// Remove null bytes
sanitized = sanitized.replace(/\0/g, '');
// Normalize unicode
sanitized = sanitized.normalize('NFKC');
return sanitized.trim();
}
// Validation schemas
export const schemas = {
user: {
create: z.object({
email: z.string().email().max(255).transform(s => s.toLowerCase()),
password: z.string()
.min(12, 'Password must be at least 12 characters')
.max(128)
.regex(/[A-Z]/, 'Password must contain uppercase letter')
.regex(/[a-z]/, 'Password must contain lowercase letter')
.regex(/[0-9]/, 'Password must contain number')
.regex(/[^A-Za-z0-9]/, 'Password must contain special character'),
name: z.string().min(1).max(100).transform(sanitizeInput),
}),
update: z.object({
name: z.string().min(1).max(100).transform(sanitizeInput).optional(),
email: z.string().email().max(255).optional(),
}),
},
pagination: z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(20),
sort: z.string().regex(/^[a-zA-Z_]+:(asc|desc)$/).optional(),
}),
};
// Validation middleware factory
export function validate<T>(schema: z.ZodSchema<T>, source: 'body' | 'query' | 'params' = 'body') {
return (req: Request, res: Response, next: NextFunction): void => {
try {
const data = source === 'body' ? req.body : source === 'query' ? req.query : req.params;
const result = schema.parse(data);
// Replace original data with validated/sanitized data
if (source === 'body') req.body = result;
else if (source === 'query') req.query = result as any;
else req.params = result as any;
next();
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({
error: {
code: 'VALIDATION_ERROR',
message: 'Request validation failed',
details: error.errors.map(e => ({
field: e.path.join('.'),
message: e.message,
})),
},
});
} else {
next(error);
}
}
};
}
// SQL injection prevention
export function sanitizeSqlInput(input: string): string {
// Never use this for actual SQL - use parameterized queries instead
// This is for logging/display purposes only
return input.replace(/['";\\]/g, '');
}
// Path traversal prevention
export function sanitizePath(path: string): string {
// Remove path traversal attempts
return path
.replace(/\.\./g, '')
.replace(/^[/\\]+/, '')
.replace(/[/\\]+/g, '/');
}
Rate Limiting Strategies
// src/security/middleware/rate-limit.ts
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
// Standard API rate limit
export const standardRateLimit = rateLimit({
store: new RedisStore({
sendCommand: (...args: string[]) => redis.call(...args),
}),
windowMs: 15 * 60 * 1000,
max: 100,
keyGenerator: (req) => req.user?.sub || req.ip,
standardHeaders: true,
legacyHeaders: false,
});
// Strict rate limit for auth endpoints
export const authRateLimit = rateLimit({
store: new RedisStore({
sendCommand: (...args: string[]) => redis.call(...args),
}),
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // 5 failed attempts per hour
keyGenerator: (req) => `auth:${req.ip}`,
skipSuccessfulRequests: true,
handler: (req, res) => {
res.status(429).json({
error: {
code: 'AUTH_RATE_LIMIT',
message: 'Too many failed authentication attempts. Try again later.',
retryAfter: 3600,
},
});
},
});
// Expensive operation rate limit
export const expensiveRateLimit = rateLimit({
store: new RedisStore({
sendCommand: (...args: string[]) => redis.call(...args),
}),
windowMs: 60 * 1000, // 1 minute
max: 10,
keyGenerator: (req) => `expensive:${req.user?.sub || req.ip}`,
});
// User-tier based rate limiting
export function tieredRateLimit(req: AuthenticatedRequest): number {
const tier = req.user?.tier || 'free';
const limits: Record<string, number> = {
free: 100,
pro: 1000,
enterprise: 10000,
};
return limits[tier] || limits.free;
}
Audit Logging
// src/security/middleware/audit.ts
import { Request, Response, NextFunction } from 'express';
import { v4 as uuidv4 } from 'uuid';
interface AuditLog {
requestId: string;
timestamp: Date;
userId?: string;
ip: string;
method: string;
path: string;
statusCode: number;
responseTime: number;
userAgent: string;
action?: string;
resource?: string;
resourceId?: string;
changes?: Record<string, { old: unknown; new: unknown }>;
}
export function auditLogger(req: Request, res: Response, next: NextFunction): void {
const requestId = req.headers['x-request-id'] as string || uuidv4();
const startTime = Date.now();
// Attach request ID to request and response
req.headers['x-request-id'] = requestId;
res.setHeader('X-Request-ID', requestId);
// Capture original end to log after response
const originalEnd = res.end;
res.end = function(...args: any[]) {
const log: AuditLog = {
requestId,
timestamp: new Date(),
userId: (req as any).user?.sub,
ip: req.ip || req.socket.remoteAddress || 'unknown',
method: req.method,
path: req.path,
statusCode: res.statusCode,
responseTime: Date.now() - startTime,
userAgent: req.headers['user-agent'] || 'unknown',
};
// Log to structured logging system
if (res.statusCode >= 400) {
console.error(JSON.stringify(log));
} else if (shouldAudit(req.method, req.path)) {
console.info(JSON.stringify(log));
}
return originalEnd.apply(res, args);
};
next();
}
function shouldAudit(method: string, path: string): boolean {
// Always audit mutations
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) return true;
// Audit sensitive reads
const sensitivePatterns = ['/admin', '/users', '/settings', '/audit'];
return sensitivePatterns.some(p => path.includes(p));
}
// Detailed action audit for sensitive operations
export async function logAuditAction(action: {
userId: string;
action: string;
resource: string;
resourceId?: string;
changes?: Record<string, { old: unknown; new: unknown }>;
metadata?: Record<string, unknown>;
}): Promise<void> {
const log = {
...action,
timestamp: new Date().toISOString(),
id: uuidv4(),
};
// Store in audit log table/service
await storeAuditLog(log);
}
Usage Examples
Implement API Security
Apply backend-api-security-patterns skill to add comprehensive security middleware to Express API
Configure Rate Limiting
Apply backend-api-security-patterns skill to implement tiered rate limiting based on user subscription
Add Audit Logging
Apply backend-api-security-patterns skill to add security audit logging for compliance
Success Output
When this skill is successfully applied, output:
✅ SKILL COMPLETE: backend-api-security-patterns
Completed:
- [x] Security middleware stack configured (helmet, CORS, rate limiting, auth)
- [x] JWT authentication implemented with token blacklist
- [x] RBAC with role inheritance and permission checking
- [x] Input validation with Zod schemas and XSS sanitization
- [x] Rate limiting (standard, auth, expensive operations)
- [x] Audit logging with request ID tracking
Outputs:
- src/security/middleware/index.ts - Security middleware configuration
- src/security/middleware/auth.ts - JWT authentication
- src/security/middleware/rbac.ts - Role-based access control
- src/security/middleware/validation.ts - Input validation and sanitization
- src/security/middleware/rate-limit.ts - Rate limiting strategies
- src/security/middleware/audit.ts - Security audit logging
Completion Checklist
Before marking this skill as complete, verify:
- Security headers configured (CSP, HSTS, referrer policy)
- CORS properly configured with allowed origins whitelist
- Rate limiting active (100 req/15min default, 5 req/hour for auth)
- JWT authentication validates token signature, issuer, audience, expiration
- Token blacklist implemented for revocation/logout
- RBAC roles defined with permission inheritance
- Input validation schemas cover all endpoints
- XSS sanitization applied to all user input
- SQL injection prevented via parameterized queries/ORM
- Audit logging captures all mutations and sensitive reads
- Security scan integrated (Snyk, TruffleHog, etc.)
Failure Indicators
This skill has FAILED if:
- ❌ SQL injection vulnerability found in code
- ❌ Hardcoded credentials/secrets detected
- ❌ CORS allows any origin (
*) - ❌ No rate limiting on auth endpoints
- ❌ JWT tokens not validated (signature, expiration)
- ❌ No token revocation mechanism
- ❌ User input not validated or sanitized
- ❌ RBAC allows privilege escalation
- ❌ Sensitive operations not audit logged
- ❌ Security headers missing or misconfigured
When NOT to Use
Do NOT use this skill when:
- Internal-only services with no external exposure - lighter security may suffice
- Prototype/development environments - defer full hardening to production
- Static content serving (CDN) - different security model applies
- Serverless functions with provider-managed security - leverage platform features
- Read-only public APIs with no sensitive data - minimal security adequate
- Services behind API gateway handling security - avoid duplication
Use alternatives instead:
- Internal services → Network segmentation + basic auth
- Prototypes → Environment-based security toggle
- Static content → CDN security features
- Serverless → AWS IAM, Azure AD integration
- Read-only public → Basic rate limiting only
Anti-Patterns (Avoid)
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Hardcoded secrets | Credential leakage, rotation impossible | Use environment variables or secret manager |
| No input validation | SQL injection, XSS vulnerabilities | Validate and sanitize all inputs with Zod |
CORS wildcard (*) | Cross-origin attacks possible | Whitelist allowed origins explicitly |
| No rate limiting on auth | Brute force attacks succeed | Strict rate limit (5/hour) on auth endpoints |
| JWT without revocation | Stolen tokens valid until expiry | Implement blacklist with Redis |
| God mode admin role | No granular permissions | Use RBAC with inheritance and fine-grained permissions |
| Manual quality gates | Security issues slip through | Automate security scans in CI/CD |
Principles
This skill embodies:
- #2 First Principles Thinking - Understand attack vectors before implementing defenses
- #4 Separation of Concerns - Security middleware separate from business logic
- #5 Eliminate Ambiguity - Clear permission definitions and severity levels
- #7 Automation - Automate security scans and validation
- #8 No Assumptions - Validate all inputs, trust nothing from client
- #9 Transparency - Audit all sensitive operations for compliance
Full Standard: CODITECT-STANDARD-AUTOMATION.md
Integration Points
- authentication-authorization - Token management
- error-handling-resilience - Security error responses
- monitoring-observability - Security metrics and alerts