Security Engineering

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.

Khalid Aboubakr
18 min read
Api SecurityOwaspInput ValidationRate LimitingSecurity HeadersCorsHelmet

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:

  1. Validate all input with strict schemas
  2. Rate limit based on endpoint sensitivity
  3. Set security headers appropriately
  4. Configure CORS restrictively
  5. Log securely for audit and debugging
  6. Monitor for anomalies

Security is not optional—it's a fundamental requirement for any production API.

Related Articles

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.

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.