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:

  • `HttpOnly`: Prohibits JavaScript from accessing the cookie via document.cookie.
  • `Secure`: Ensures the cookie is transmitted only over encrypted HTTPS connections.
  • `SameSite=Lax` (or `Strict`): Restricts the browser from sending the cookie in cross-site requests, neutralizing CSRF attacks.
  • Narrow Domain Scope: Never set wildcards like Domain=.example.com if untrusted subdomains or user-generated content share the parent domain.
  • ts
    // 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:

    ts
    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.

  • Implement IP-based rate limiting using in-memory or Redis sliding windows.
  • Integrate Cloudflare Turnstile or reCAPTCHA v3 on sensitive submission forms.
  • Configure automated alert thresholds to detect anomalous spikes in 401 and 429 status codes.
  • Security is not an afterthought; it is the foundation upon which resilient, trustworthy software is constructed.