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