Skip to main content

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

  1. Review the patterns and examples below
  2. Apply the relevant patterns to your implementation
  3. Follow the best practices outlined in this skill

API security hardening, authentication patterns, and vulnerability prevention.

Core Capabilities

  1. Authentication - JWT, OAuth2, API keys
  2. Authorization - RBAC, ABAC, permissions
  3. Input Validation - Schema validation, sanitization
  4. Rate Limiting - Request throttling, abuse prevention
  5. 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-PatternProblemSolution
Hardcoded secretsCredential leakage, rotation impossibleUse environment variables or secret manager
No input validationSQL injection, XSS vulnerabilitiesValidate and sanitize all inputs with Zod
CORS wildcard (*)Cross-origin attacks possibleWhitelist allowed origins explicitly
No rate limiting on authBrute force attacks succeedStrict rate limit (5/hour) on auth endpoints
JWT without revocationStolen tokens valid until expiryImplement blacklist with Redis
God mode admin roleNo granular permissionsUse RBAC with inheritance and fine-grained permissions
Manual quality gatesSecurity issues slip throughAutomate 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