CORS & Security Headers
CORS (Express)
import cors from 'cors';
// Restrictive (recommended) app.use(cors({ origin: ['https://myapp.com', 'https://admin.myapp.com'], methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, maxAge: 86400, // Preflight cache: 24h }));
// Dynamic origin app.use(cors({ origin: (origin, callback) => { const allowed = ALLOWED_ORIGINS.includes(origin!) || !origin; // !origin = same-origin callback(null, allowed ? origin : false); }, credentials: true, }));
Security Headers (Helmet.js)
import helmet from 'helmet';
app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'", 'https://cdn.example.com'], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", 'data:', 'https:'], connectSrc: ["'self'", 'https://api.example.com'], frameSrc: ["'none'"], objectSrc: ["'none'"], }, }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }, referrerPolicy: { policy: 'strict-origin-when-cross-origin' }, }));
CSRF Protection
import csrf from 'csurf';
// For server-rendered forms (session-based apps) app.use(csrf({ cookie: true })); app.get('/form', (req, res) => { res.render('form', { csrfToken: req.csrfToken() }); });
// For SPAs: use SameSite cookies + custom header // No csrf library needed — rely on: // 1. SameSite=Strict/Lax cookies // 2. Check Origin/Referer header matches // 3. Custom header requirement (X-Requested-With)
Secure Cookies
app.use(session({ cookie: { httpOnly: true, // No JS access secure: true, // HTTPS only sameSite: 'lax', // CSRF protection maxAge: 24 * 60 * 60 * 1000, domain: '.myapp.com', // Shared across subdomains }, }));
Spring Boot Security Headers
@Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .cors(cors -> cors.configurationSource(corsConfig())) .headers(headers -> headers .contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'")) .referrerPolicy(ref -> ref.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN)) .frameOptions(frame -> frame.deny()) ) .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())) .build(); }
@Bean CorsConfigurationSource corsConfig() { var config = new CorsConfiguration(); config.setAllowedOrigins(List.of("https://myapp.com")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); config.setAllowCredentials(true); var source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; }
Key Headers Reference
Header Purpose Recommended Value
Strict-Transport-Security
Force HTTPS max-age=31536000; includeSubDomains
Content-Security-Policy
Restrict resource loading default-src 'self'
- specifics
X-Content-Type-Options
Prevent MIME sniffing nosniff
X-Frame-Options
Prevent clickjacking DENY
Referrer-Policy
Control referrer info strict-origin-when-cross-origin
Permissions-Policy
Restrict browser features camera=(), microphone=()
Anti-Patterns
Anti-Pattern Fix
Access-Control-Allow-Origin: * with credentials Specify exact origins
No CSP header Add Content-Security-Policy
CSRF token in URL query params Use header or hidden form field
SameSite=None without Secure
Always pair SameSite=None with Secure
Missing HSTS Enable with long max-age and preload
Production Checklist
-
CORS: specific origins, no wildcard with credentials
-
Helmet.js (or equivalent) for all security headers
-
CSP configured and tested
-
HSTS with preload
-
Secure, HttpOnly, SameSite cookies
-
CSRF protection for state-changing requests