Skip to content

Network Security

CORS, CSP, MITM attacks, firewalls — practical security at the network layer that every developer should understand.

CORS — Cross-Origin Resource Sharing

CORS is the browser’s way of asking: “Is this frontend allowed to call this API?”

Real-World Analogy

Like the security layers at an airport — firewall (entrance gate check), packet inspection (baggage scanner), authentication (passport control), and encryption (sealed diplomatic pouches). Multiple layers of defense.

When app.example.com makes a request to api.example.com, the browser sends a preflight request:

// Browser sends preflight (automatically):
// OPTIONS /api/users
// Origin: https://app.example.com
// Access-Control-Request-Method: POST
// Access-Control-Request-Headers: Content-Type, Authorization

// Server must respond with:
// Access-Control-Allow-Origin: https://app.example.com
// Access-Control-Allow-Methods: GET, POST, PUT, DELETE
// Access-Control-Allow-Headers: Content-Type, Authorization
// Access-Control-Max-Age: 86400

// Express middleware
import cors from 'cors';

app.use(
	cors({
		origin: ['https://app.example.com', 'https://staging.example.com'],
		methods: ['GET', 'POST', 'PUT', 'DELETE'],
		allowedHeaders: ['Content-Type', 'Authorization'],
		credentials: true, // allow cookies
		maxAge: 86400 // cache preflight for 24h
	})
);

Never use Access-Control-Allow-Origin: * with credentials: true. This lets any website make authenticated requests to your API. Always whitelist specific origins.

Security Headers

// Essential security headers for any web application
const securityHeaders = {
	// Prevent clickjacking (embedding your site in an iframe)
	'X-Frame-Options': 'DENY',

	// Prevent MIME type sniffing
	'X-Content-Type-Options': 'nosniff',

	// Force HTTPS for 1 year
	'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',

	// Control what the browser can load
	'Content-Security-Policy': [
		"default-src 'self'",
		"script-src 'self' 'nonce-abc123'",
		"style-src 'self' 'unsafe-inline'",
		"img-src 'self' data: https:",
		"connect-src 'self' https://api.example.com",
		"frame-ancestors 'none'"
	].join('; '),

	// Control what info is sent in Referer header
	'Referrer-Policy': 'strict-origin-when-cross-origin',

	// Opt into browser security features
	'Permissions-Policy': 'camera=(), microphone=(), geolocation=()'
};

Content Security Policy (CSP)

CSP tells the browser exactly what resources your page is allowed to load. This prevents XSS attacks — even if an attacker injects a script tag, the browser won’t execute it.

// Without CSP: injected <script> runs freely
// With CSP: browser blocks scripts not in the whitelist

// Nonce-based CSP (recommended)
// Server generates a random nonce for each request
import crypto from 'node:crypto';

function generateCSP(): { header: string; nonce: string } {
	const nonce = crypto.randomBytes(16).toString('base64');

	return {
		nonce,
		header: [
			"default-src 'self'",
			`script-src 'nonce-${nonce}' 'strict-dynamic'`,
			"style-src 'self' 'unsafe-inline'",
			"object-src 'none'",
			"base-uri 'self'"
		].join('; ')
	};
}

// In your HTML template:
// <script nonce="<%= nonce %>">...</script>
// Scripts without the matching nonce are blocked

Man-in-the-Middle (MITM) Attacks

An attacker positions themselves between client and server, intercepting or modifying traffic.

Defenses:

  • TLS everywhere — encrypted traffic can’t be read or modified
  • HSTS — prevents SSL stripping (downgrading HTTPS to HTTP)
  • Certificate pinning — mobile apps verify the exact certificate, not just the chain
  • DNSSEC — prevents DNS spoofing that could redirect traffic

Rate Limiting at the Network Layer

// Simple token bucket rate limiter
class RateLimiter {
	private tokens: Map<string, { count: number; resetAt: number }> = new Map();

	constructor(
		private maxRequests: number,
		private windowMs: number
	) {}

	isAllowed(clientIP: string): boolean {
		const now = Date.now();
		const bucket = this.tokens.get(clientIP);

		if (!bucket || bucket.resetAt < now) {
			this.tokens.set(clientIP, { count: 1, resetAt: now + this.windowMs });
			return true;
		}

		if (bucket.count >= this.maxRequests) {
			return false; // 429 Too Many Requests
		}

		bucket.count++;
		return true;
	}
}

// Usage: 100 requests per minute per IP
const limiter = new RateLimiter(100, 60_000);

Defense in depth: No single security measure is enough. Use TLS + security headers + CSP + rate limiting + input validation together. Each layer catches what the others miss.

Key Takeaways

  1. CORS is a browser feature, not a server security measure — APIs are still callable from non-browser clients
  2. Security headers are cheap insurance — add them to every response
  3. CSP prevents XSS even when your code has injection vulnerabilities
  4. TLS + HSTS together prevent MITM and SSL stripping
  5. Rate limiting at the network layer is your first line of defense against abuse