API Security Hardening: A Practitioner's Guide
Secure your APIs with rate limiting, input validation, and CORS configuration. Production-tested checklist covering authentication, encryption, and error handling.
Introduction
APIs are the attack surface of modern applications. Every endpoint is a potential entry point for attackers. This guide covers practical hardening techniques I've applied to secure APIs in production.
Input Validation
Schema-Based Validation
import { z } from 'zod'; // Define strict schemas const createUserSchema = z.object({ email: z.string() .email('Invalid email format') .max(255) .transform(email => email.toLowerCase()), password: z.string() .min(12, 'Password must be at least 12 characters') .regex(/[A-Z]/, 'Password must contain uppercase') .regex(/[a-z]/, 'Password must contain lowercase') .regex(/[0-9]/, 'Password must contain number') .regex(/[^A-Za-z0-9]/, 'Password must contain special character'), name: z.string() .min(1) .max(100) .regex(/^[a-zA-Z\s'-]+$/, 'Name contains invalid characters'), dateOfBirth: z.string() .regex(/^\d{4}-\d{2}-\d{2}$/) .transform(str => new Date(str)) .refine(date => date < new Date(), 'Date must be in the past'), }); // Validation middleware function validateBody<T extends z.ZodType>(schema: T) { return (req: Request, res: Response, next: NextFunction) => { const result = schema.safeParse(req.body); if (!result.success) { return res.status(400).json({ error: 'Validation failed', details: result.error.issues.map(issue => ({ field: issue.path.join('.'), message: issue.message, })), }); } req.body = result.data; next(); }; }
Parameter Pollution Protection
// Prevent HTTP Parameter Pollution function preventParamPollution(allowedParams: string[]) { return (req: Request, res: Response, next: NextFunction) => { for (const key of Object.keys(req.query)) { if (Array.isArray(req.query[key])) { if (allowedParams.includes(key)) { // Take only first value req.query[key] = req.query[key][0]; } else { return res.status(400).json({ error: `Duplicate parameter not allowed: ${key}`, }); } } } next(); }; }
Rate Limiting
Tiered Rate Limiting
import rateLimit from 'express-rate-limit'; import RedisStore from 'rate-limit-redis'; // Different limits for different endpoints const rateLimiters = { // Strict limit for auth endpoints auth: rateLimit({ store: new RedisStore({ client: redisClient }), windowMs: 15 * 60 * 1000, max: 5, keyGenerator: (req) => req.body.email || req.ip, handler: (req, res) => { res.status(429).json({ error: 'Too many attempts', retryAfter: Math.ceil(req.rateLimit.resetTime / 1000), }); }, }), // Moderate limit for general API api: rateLimit({ store: new RedisStore({ client: redisClient }), windowMs: 60 * 1000, max: 100, keyGenerator: (req) => req.user?.id || req.ip, }), // Strict limit for expensive operations expensive: rateLimit({ store: new RedisStore({ client: redisClient }), windowMs: 60 * 60 * 1000, max: 10, keyGenerator: (req) => req.user?.id || req.ip, }), }; // Apply appropriately app.use('/api/auth', rateLimiters.auth); app.use('/api', rateLimiters.api); app.use('/api/reports/generate', rateLimiters.expensive);
Security Headers
import helmet from 'helmet'; app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'strict-dynamic'"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", 'data:', 'https:'], connectSrc: ["'self'", process.env.API_URL], fontSrc: ["'self'"], objectSrc: ["'none'"], mediaSrc: ["'self'"], frameSrc: ["'none'"], baseUri: ["'self'"], formAction: ["'self'"], frameAncestors: ["'none'"], upgradeInsecureRequests: [], }, }, crossOriginEmbedderPolicy: true, crossOriginOpenerPolicy: { policy: 'same-origin' }, crossOriginResourcePolicy: { policy: 'same-origin' }, dnsPrefetchControl: { allow: false }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true, }, noSniff: true, referrerPolicy: { policy: 'strict-origin-when-cross-origin' }, xssFilter: true, })); // Additional custom headers app.use((req, res, next) => { res.setHeader('X-Request-ID', req.id); res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('Permissions-Policy', 'geolocation=(), camera=(), microphone=()'); next(); });
CORS Configuration
import cors from 'cors'; const corsOptions: cors.CorsOptions = { origin: (origin, callback) => { const allowedOrigins = [ 'https://app.example.com', 'https://admin.example.com', ]; if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'], exposedHeaders: ['X-Request-ID', 'X-RateLimit-Remaining'], credentials: true, maxAge: 86400, }; app.use(cors(corsOptions));
Request Logging and Monitoring
// Secure logging (never log sensitive data) function createAuditLog(req: Request, res: Response) { return { timestamp: new Date().toISOString(), requestId: req.id, method: req.method, path: req.path, userId: req.user?.id, ip: req.ip, userAgent: req.headers['user-agent'], statusCode: res.statusCode, responseTime: res.responseTime, // Never log: passwords, tokens, PII in query params }; }
Conclusion
API security requires defense in depth:
- Validate all input with strict schemas
- Rate limit based on endpoint sensitivity
- Set security headers appropriately
- Configure CORS restrictively
- Log securely for audit and debugging
- Monitor for anomalies
Security is not optional—it's a fundamental requirement for any production API.
Related Articles
Security Engineering21 min read
Authentication and Authorization in Production Systems
Implement secure JWT authentication with refresh token rotation, RBAC, and OAuth 2.0 flows. Production patterns from healthcare and government systems.
Security Engineering16 min read
Protecting Against Injection Attacks: SQL, NoSQL, and XSS
Prevent SQL injection, NoSQL injection, and XSS attacks with validated code patterns. Covers parameterized queries, input sanitization, and CSP configuration.
Security Engineering15 min read
Secure Session Management: Patterns and Pitfalls
Implement secure session management with proper cookie settings, token rotation, and logout flows. Covers session fixation, hijacking prevention, and multi-device handling.
Software Architecture19 min read
Building Resilient Distributed Systems: Patterns for Fault Tolerance
Build resilient distributed systems with circuit breakers, retries, and timeouts. Production patterns for handling failures, cascading errors, and maintaining availability.
Backend Design19 min read
API Design: Choosing Between REST, GraphQL, and gRPC
Compare REST, GraphQL, and gRPC APIs with performance benchmarks and use cases. Learn which API style fits your project based on real production experience.