Security
Security Headers
HTTP security headers instruct browsers how to behave, protecting users from common attacks. A single header can prevent entire categories of vulnerabilities. This guide covers the essential headers every web application should implement.
Content-Security-Policy (CSP)
CSP is the most powerful header for preventing XSS attacks. It tells the browser which scripts, styles, images, and other resources are trusted. The browser only loads resources from trusted sources and blocks everything else.
Without CSP: An attacker injects a malicious script. The browser loads and executes it. The attacker steals user data.
With CSP: An attacker injects a script. The browser checks the CSP policy, sees the injected script is not whitelisted, and blocks it. The attack fails.
Example CSP header: Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self'. This allows scripts and styles only from the same origin or the CDN, blocking inline scripts and external sources.
Content-Security-Policy-Report-Only first. This logs violations without blocking them, letting you test the policy and fix issues before enforcing it. After testing, switch to the enforcement header.HTTP Strict-Transport-Security (HSTS)
HSTS tells the browser to only connect to your site via HTTPS, never HTTP. This prevents man-in-the-middle attacks on the initial connection.
Without HSTS: A user visits your site by typing the URL without https://. The browser connects via HTTP. An attacker intercepts the connection and redirects to a fake site. The user enters credentials, which the attacker steals.
With HSTS: The browser remembers that your site only uses HTTPS. On the next visit, the browser enforces HTTPS internally, preventing the downgrade attack.
Example: Strict-Transport-Security: max-age=31536000; includeSubDomains; preload. This tells the browser to enforce HTTPS for one year, including subdomains, and adds the site to a preload list (browsers preload it by default).
X-Frame-Options (Clickjacking Protection)
Clickjacking is an attack where a malicious site loads your site in a hidden frame and tricks users into clicking something on your site (like "transfer money") while they think they're clicking something on the malicious site.
X-Frame-Options prevents this: X-Frame-Options: DENY tells the browser not to allow your site to be framed at all. Use SAMEORIGIN if you frame your own site in other pages.
X-Content-Type-Options
Browsers sometimes guess file types based on content instead of the Content-Type header. This can cause JavaScript to be executed from files you intended as downloads.
X-Content-Type-Options: nosniff tells the browser: don't guess the type, trust the Content-Type header. This prevents MIME sniffing attacks.
Referrer-Policy
When a user clicks a link, the browser sends the referring page URL. This can leak sensitive information (a URL might contain a session token or personal data).
Referrer-Policy: strict-origin-when-cross-origin tells the browser: when linking to other sites, don't send the full referring URL, just the origin. This prevents leaking sensitive path information while allowing the receiving site to know where you came from.
Permissions-Policy
Modern browsers have powerful features like camera, microphone, geolocation, and payment APIs. A compromised third-party script could abuse these.
Permissions-Policy: geolocation=(), microphone=(), camera=() tells the browser: don't allow geolocation, microphone, or camera access. This is useful if you don't need these features.
Cross-Origin Resource Sharing (CORS)
CORS is a mechanism that allows scripts from one domain to request resources from another. Without CORS, browsers block cross-origin requests by default (same-origin policy).
If you have a frontend on example.com and an API on api.example.com, the frontend needs to make cross-origin requests. You configure CORS headers to allow this.
Access-Control-Allow-Origin: https://example.com tells the browser: requests from example.com are allowed to access this resource. Only specify origins you trust. Access-Control-Allow-Origin: * allows any site—only use this for public APIs that don't require authentication.
Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true. This is a security bug—it allows any site to make authenticated requests on behalf of users. Avoid combining wildcard origins with credentials.Key Security Headers Comparison
| Header | Purpose | Recommended Value | Impact if Missing |
|---|---|---|---|
| Content-Security-Policy | XSS prevention | default-src 'self' | Inline scripts can execute |
| Strict-Transport-Security | HTTPS enforcement | max-age=31536000; includeSubDomains | Downgrade to HTTP possible |
| X-Frame-Options | Clickjacking prevention | DENY | Site can be framed/clickjacked |
| X-Content-Type-Options | MIME sniffing prevention | nosniff | Browser may execute wrong type |
| Referrer-Policy | Information leakage prevention | strict-origin-when-cross-origin | URLs with tokens exposed |
| Permissions-Policy | Feature limitation | geolocation=(), microphone=() | Third-party scripts can abuse features |
Setting Headers in Next.js
In Next.js, set security headers in next.config.js:
async headers() {
return [{
source: '/:path*',
headers: [
{ key: 'Strict-Transport-Security', value: 'max-age=31536000' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
],
}],
}
For CSP, you might want dynamic nonce values to prevent inline script injection. This requires middleware to generate nonces and apply them to the CSP header.
Testing Your Headers
Use securityheaders.com to test your site. It grades your security headers and recommends improvements. It's a quick way to catch missing headers.
You can also use curl to inspect headers: curl -I https://example.com | grep -i security. This shows the security headers your site sends.
Common Mistakes
Setting CSP too restrictive: A policy that blocks your own styles or scripts defeats the purpose. Test thoroughly before enforcing.
Wildcard CORS origins with credentials: Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true is a security vulnerability.
Not including HSTS preload: Without the preload directive, HSTS only works after the first visit. Include it to be in the browser preload list.
Headers as Defense in Depth
Security headers don't fix underlying vulnerabilities—they mitigate them at the browser level. An XSS vulnerability is still bad code; CSP just prevents attackers from exploiting it. Your primary goal is writing secure code; headers are an additional layer.
Implement all major security headers as a baseline. Test them thoroughly. Monitor your CSP reports to detect if attackers are trying to exploit vulnerabilities. As you find and fix bugs, your header policies can become stricter. Security headers are part of defense in depth—multiple layers protecting your users.