Building with Defense in Depth
In modern web development, shipping fast should never mean shipping insecurely. From cross-site scripting (XSS) and cross-site request forgery (CSRF) to token leakage and session hijacking, web applications are attacked constantly by automated scrapers and malicious actors.
Adopting a "defense in depth" posture ensures that even if one layer of defense is compromised, secondary safeguards prevent catastrophic data breaches.
Here is the essential hardening checklist we apply across our production web properties.
1. Authentication Cookies: Isolation and Flags
Storing JWT tokens or session credentials inside browser localStorage is a dangerous anti-pattern. Any XSS vulnerability on your site—including rogue third-party npm scripts—can instantly read localStorage and exfiltrate user credentials.
Secure Cookie Standards:
Always store authentication tokens inside HTTP cookies configured with strict security flags:
document.cookie.Domain=.example.com if untrusted subdomains or user-generated content share the parent domain.// Express / Node.js secure cookie configuration
res.cookie('session_token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
domain: process.env.COOKIE_DOMAIN || undefined,
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
});2. Content Security Policy (CSP) Headers
A Content Security Policy (CSP) provides an authoritative whitelist telling the browser which domains are permitted to load scripts, stylesheets, fonts, and images on your site.
A properly configured CSP neutralizes XSS attacks by refusing to execute unauthorized inline scripts or third-party tracking injections:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://pagead2.googlesyndication.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-ancestors 'none';3. Strict Input Validation with Zod
Never trust client input. Every endpoint must validate incoming payloads against a strict schema before executing database operations:
import { z } from 'zod';
export const ContactFormSchema = z.object({
name: z.string().trim().min(2).max(100),
email: z.string().trim().email(),
phone: z.string().trim().max(20).optional(),
message: z.string().trim().min(10).max(2000),
});Using schema validation libraries ensures that malformed payloads, injection vectors, and prototype pollution attacks are rejected at the application perimeter with clean HTTP 400 responses.
4. Rate Limiting and Bot Defense
Public endpoints—particularly login, password reset, and contact forms—must be protected against brute-force attacks and credential stuffing.
Security is not an afterthought; it is the foundation upon which resilient, trustworthy software is constructed.