Web Security Patterns
Content Security Policy (CSP)
# Strict nonce-based CSP (recommended)
Content-Security-Policy:
default-src 'self';
script-src 'nonce-{RANDOM_BASE64}' 'strict-dynamic';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-src 'none';
object-src 'none';
base-uri 'self';
upgrade-insecure-requests;
// Next.js: generate nonce per request
import crypto from 'crypto';
export function middleware(req) {
const nonce = crypto.randomBytes(16).toString('base64');
const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none';`;
const res = NextResponse.next();
res.headers.set('Content-Security-Policy', csp);
res.headers.set('x-nonce', nonce);
return res;
}
Security Headers
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
XSS Prevention
// WRONG: direct innerHTML injection
element.innerHTML = userInput;
// RIGHT: textContent for plain text
element.textContent = userInput;
// RIGHT: sanitize HTML when HTML is needed
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href', 'title']
});
// React: dangerouslySetInnerHTML must always sanitize
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />
Trusted Types
// Enforce via CSP: require-trusted-types-for 'script'
if (window.trustedTypes && window.trustedTypes.createPolicy) {
const policy = trustedTypes.createPolicy('default', {
createHTML: (input) => DOMPurify.sanitize(input),
createScriptURL: (url) => {
if (new URL(url).origin === location.origin) return url;
throw new Error('Untrusted script URL');
}
});
element.innerHTML = policy.createHTML(userInput);
}
CORS
// Server (Express)
import cors from 'cors';
app.use(cors({
origin: ['https://app.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400
}));
CSRF Protection
// Double-submit cookie pattern
// Server sets csrf cookie on login
// Client reads cookie and sends as header
async function fetchWithCSRF(url, options = {}) {
const token = document.cookie.match(/csrf=([^;]+)/)?.[1];
return fetch(url, {
...options,
headers: { ...options.headers, 'X-CSRF-Token': token }
});
}
Subresource Integrity (SRI)
<!-- Hash ensures CDN file hasn't been tampered with -->
<script
src="https://cdn.example.com/lib.min.js"
integrity="sha384-abc123..."
crossorigin="anonymous">
</script>
# Generate SRI hash
openssl dgst -sha384 -binary lib.min.js | openssl base64 -A
iframe Sandbox
<!-- Allow only what's needed -->
<iframe
src="https://widget.example.com"
sandbox="allow-scripts allow-same-origin"
allow="payment"
referrerpolicy="no-referrer"
loading="lazy">
</iframe>
Cookie Security
Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600