The Power of HTTP Headers

How HTTP headers help prevent XSS, clickjacking, private data leaks, and session exposure, with examples for browser permissions, logout cleanup, and Spring Security.

Alexandru DancauOctober 29, 2026 · 7 min read
An HTTP response document connects Content-Type, Cache-Control, and CSP headers to content handling, cache restrictions, and browser security controls.

A user uploads a document. The application checks permissions and returns the file. Everything looks correct, but the response says Content-Type: text/html.

The browser treats the file as a web page. If it contains attacker-controlled JavaScript and no effective policy blocks it, that code can run under the application's origin.

HTTP headers help control what the browser can execute, store, and share. They add protection around the content our application delivers. Let's look at the security problems they help solve.

Keep uploaded content from becoming executable code

Content-Type tells the client what kind of content it received:

Content-Type: application/json

Use application/json for JSON, application/pdf for PDFs, and text/plain; charset=utf-8 for plain text. Labeling untrusted content as HTML can turn a file-serving feature into a cross-site scripting problem, usually called XSS. MDN on Content-Type

Pair the correct type with:

X-Content-Type-Options: nosniff

Browsers sometimes inspect content to infer its type. nosniff prevents that guessing and blocks scripts or stylesheets served with an unexpected type. It does not repair a response incorrectly labeled text/html; the server must send the right type first. MDN on nosniff

For a private PDF intended as a download:

Content-Type: application/pdf
Content-Disposition: attachment; filename="report.pdf"
X-Content-Type-Options: nosniff
Cache-Control: no-store

attachment asks the browser to download the file. It does not make a malicious file safe to open. MDN on Content-Disposition

Also, an upload's declared Content-Type comes from the caller. Validate allowed formats, file contents, and size limits on the server. Do not trust that label as proof of what the file contains. OWASP file upload guidance

Restrict scripts with Content Security Policy

Suppose an attacker finds a bug that inserts a script into a page. Content Security Policy, or CSP, can limit which scripts the browser runs and reduce the impact of that injection.

For a simple page that loads external scripts and other resources from its own origin:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'

Here, 'self' means the page's own origin. The policy restricts resource loading, blocks embedded objects, prevents changes through an HTML base element, and prevents framing. It also blocks inline scripts and styles.

Adapt the policy to the application. A same-origin script allowlist still trusts scripts served from that origin. If inline scripts are necessary, use approved hashes or unpredictable nonces generated for each response. Avoid adding 'unsafe-inline' just to silence errors. MDN CSP guide

Test a new policy using Content-Security-Policy-Report-Only, inspect violations, then enable enforcement. Report-only mode observes violations without blocking them.

CSP belongs on the HTML response. Adding it only to a JSON API does not protect a separately served frontend. Keep output encoding and safe rendering in place too. MDN CSP reference

Prevent clickjacking

An attacker can place our application inside an invisible frame and trick someone into clicking a real button, such as Delete, while showing a different interface. This is clickjacking.

The CSP directive from the previous example prevents embedding:

Content-Security-Policy: frame-ancestors 'none'

Keep it inside the complete CSP policy. X-Frame-Options: DENY provides a fallback for browsers that do not support frame-ancestors. If legitimate embedding is required, allow only the origins that need it. OWASP framing guidance

Disable browser features the page does not need

A document dashboard probably does not need the camera, microphone, or the user's location. Make those limits explicit on the HTML response:

Permissions-Policy: camera=(), microphone=(), geolocation=()

The empty allowlist () disables each feature for the page and its embedded frames, where supported. This reduces the browser capabilities available to code running there, including third-party scripts.

Choose restrictions that fit the application and check support in your target browsers. Permissions Policy controls specific features; it does not make untrusted scripts safe or replace CSP. MDN on Permissions-Policy

Keep private responses out of caches

Alice's private report must not become a response that a shared cache serves to Bob. For sensitive account pages and document responses, use:

Cache-Control: no-store

This tells private and shared HTTP caches not to store the response. Two similar-looking settings mean different things:

Setting Meaning
no-store Do not store the response in HTTP caches
no-cache Storage is allowed, but reuse requires validation
private Storage is allowed in a private cache, such as the browser's, but not a shared cache

Check that CDN rules respect the policy. Cache headers do not replace authorization or remove copies someone already downloaded. MDN on Cache-Control

Keep connections on HTTPS

A redirect moves an HTTP request to HTTPS. HTTP Strict Transport Security, or HSTS, tells the browser to use HTTPS for future connections to the host:

Strict-Transport-Security: max-age=300

This short example lasts five minutes. Start with a small duration during rollout, then increase it after confirming HTTPS works reliably. Browsers accept HSTS only over HTTPS.

Add includeSubDomains only when all affected subdomains support HTTPS. Without an existing HSTS policy or preload entry, the first HTTP visit remains exposed before the redirect. MDN on HSTS

Protect the session cookie

A session cookie identifies the user's authenticated session. Its response header should define how the browser handles it:

Set-Cookie: session=opaque-value; Path=/; Secure; HttpOnly; SameSite=Lax
  • Secure restricts transmission to secure connections.
  • HttpOnly prevents JavaScript from reading the cookie through browser cookie APIs.
  • SameSite=Lax limits when the browser includes it in requests from other sites, while allowing some navigations.

HttpOnly makes cookie theft harder, but injected JavaScript can still make authenticated requests. SameSite adds CSRF protection; it does not replace the application's CSRF defenses. MDN on Set-Cookie

Clean up browser data after logout

Alice signs out on a shared computer. Ending her session does not automatically remove report data the application stored in the browser.

After invalidating the session on the server, the successful logout response over HTTPS can include:

Clear-Site-Data: "cache", "cookies", "storage"

This asks the browser to clear the selected data types. "storage" includes localStorage, IndexedDB, and service worker registrations for the response's origin. Browser cleanup complements server logout; it does not invalidate a session by itself.

Choose the scope deliberately: "cookies" affects the entire registered domain, including subdomains, and clearing storage can remove preferences or offline work. Use it for the intended logout flow and check browser support for the selected directives. MDN on Clear-Site-Data

Limit what other sites can read

CORS headers tell the browser which other origins may read responses from our API:

Access-Control-Allow-Origin: https://dashboard.example.com
Access-Control-Allow-Credentials: true
Vary: Origin

For requests using cookies, allow a specific trusted origin rather than *. If selecting origins dynamically, validate them against an allowlist and return Vary: Origin. Configure preflight permissions for the required methods and headers too.

CORS is not authentication or authorization. A command-line client does not need the browser's permission to call an API, and some unwanted browser requests can reach the server even when their responses cannot be read. MDN CORS guide

For more detail, see Understanding CSRF and CORS.

For sensitive pages that should not disclose their URL through the referrer header, use Referrer-Policy: no-referrer. Keep secrets out of URLs regardless. MDN on Referrer-Policy

Configure the response that reaches the browser

Spring Security provides defaults including nosniff, frame protection, cache prevention, and HSTS on requests recognized as secure. CSP needs an application-specific policy.

Inside the existing SecurityFilterChain configuration, before http.build(), add:

http.headers(headers -> headers
    .contentSecurityPolicy(csp -> csp.policyDirectives(
        "default-src 'self'; script-src 'self'; "
        + "object-src 'none'; base-uri 'none'; "
        + "frame-ancestors 'none'"))
);

This Spring Security 6.5 example keeps the other header defaults. Adjust the policy to the resources the page needs. Spring Security header configuration

If a separate web server serves the frontend, configure the HTML headers there too. Choose where each policy is set and inspect the final response after proxies and CDNs.

Use browser developer tools to check an authenticated page, a download, and an error response. Confirm that private content uses the intended cache policy and that CSP blocks unexpected scripts without breaking legitimate features.

Also check that restricted browser features are unavailable and that a successful logout clears the intended browser data while ending the server session.