KAHIBARO
Discord Login Register

15.14 Security Headers

Why Security Headers Matter

Security headers are small pieces of information that the server sends with HTTP responses. Browsers read these headers and change how they behave. With a few lines of configuration you can:

Security headers are defense in depth, not a replacement for secure code.
You must still validate input, escape output, and fix vulnerabilities in your app.

We will look at the most important headers backend developers should know, how they work, and simple examples.


Common Security Headers Overview

Here is a quick overview of important security headers you will see or use:

Header nameMain purpose
Content-Security-PolicyLimits what resources / scripts can run
X-Frame-OptionsControls if the site can be inside an iframe
X-Content-Type-OptionsPrevents content type sniffing
Referrer-PolicyControls how much referrer info is sent
Strict-Transport-SecurityForces HTTPS in the browser
X-XSS-ProtectionLegacy XSS filter control
Permissions-PolicyControls access to powerful browser features
Cross-Origin-Opener-PolicyIsolation for security and performance
Cross-Origin-Resource-PolicyControls cross origin resource loading
Cross-Origin-Embedder-PolicyStronger isolation for some apps
Access-Control-*CORS headers, control cross origin requests

We will focus on the ones that matter most for typical backend APIs and web apps.


X-Content-Type-Options

Browsers try to guess the content type of a response. Sometimes they ignore the Content-Type header and "sniff" the type from the content. This can be dangerous.

For example:

X-Content-Type-Options tells the browser not to guess and to trust your Content-Type header.

Typical value:

http
X-Content-Type-Options: nosniff

Always set
X-Content-Type-Options: nosniff
on all responses that return user controlled content or file downloads.

Example: Simple HTTP response

http
HTTP/1.1 200 OK
Content-Type: text/css
X-Content-Type-Options: nosniff
body { background: #fff; }

Even if the CSS file contains something that might look like HTML or JavaScript, the browser will treat it only as CSS.


X-Frame-Options

Attackers can put your site inside an <iframe> on their own site and trick users. This is called a clickjacking attack. The user thinks they click a harmless button, but they actually click something on your site, for example "Delete account".

X-Frame-Options tells the browser if your site is allowed to be loaded inside an iframe.

Common values:

ValueMeaning
DENYNever allow this page to be inside any frame/iframe
SAMEORIGINOnly allow in a frame on the same origin
ALLOW-FROM uriAllow in a frame only from specific origin (legacy, not well supported)

Typical safe choice for most apps:

http
X-Frame-Options: SAMEORIGIN

If your app never needs to be in an iframe at all, you can use:

http
X-Frame-Options: DENY

Example: Protecting a login page

http
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: DENY
<html>
  <body>Login form here...</body>
</html>

Now another site cannot embed your login page in an invisible iframe and trick the user.


Strict-Transport-Security (HSTS)

If your site uses HTTPS, you still have a small problem. A user might type http://example.com or click an old http link. The browser first connects with HTTP, then you redirect to HTTPS. That first HTTP request can be attacked, for example:

Strict-Transport-Security (HSTS) tells the browser:

For the next N seconds, only use HTTPS for this domain.

Once the browser sees this header, it will:

Typical value:

http
Strict-Transport-Security: max-age=31536000; includeSubDomains

Explanation:

Only enable HSTS after your site is correctly served over HTTPS.
If you misconfigure it with a long max-age, you can lock users out until it expires.

Example: Response with HSTS

http
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

The optional preload directive is used when you submit your domain to the browser vendors' HSTS preload list. That is more advanced and must be done carefully.


Content-Security-Policy (CSP)

Content-Security-Policy is one of the most powerful security headers. It tells the browser what it is allowed to load and execute.

You can use CSP to:

General structure:

http
Content-Security-Policy: <directive1> <value>; <directive2> <value>; ...

Each directive controls a type of content.

Common directives:

DirectiveControlsExample value
default-srcFallback for all content types'self'
script-srcJavaScript sources'self' https://cdn.example.com
style-srcCSS sources'self' 'unsafe-inline'
img-srcImage sources'self' data:
connect-srcAJAX, WebSocket, fetch connections'self' https://api.example.com
frame-ancestorsWho can embed this page in a frame'self' or none

Some important keywords:

A strong CSP often breaks existing pages that use inline scripts or styles.
Start with report only mode to test CSP:
Content-Security-Policy-Report-Only: ...

Example: Basic CSP for a simple app

You have an app that:

You can start with:

http
Content-Security-Policy: default-src 'self';

This means: all content types (scripts, styles, images, etc.) can load only from the same origin.

Example: CSP for app with CDN

You serve scripts and styles from https://cdn.example.com and images from anywhere (user avatars, external sites).

http
Content-Security-Policy: default-src 'self';
  script-src 'self' https://cdn.example.com;
  style-src 'self' https://cdn.example.com;
  img-src 'self' https:;

Broken into readable form:

http
Content-Security-Policy: default-src 'self';
                         script-src 'self' https://cdn.example.com;
                         style-src 'self' https://cdn.example.com;
                         img-src 'self' https:;

Here:

Referrer-Policy

When you click a link, the browser often sends a Referer header (note the misspelling) to the new site, containing the URL of the page you came from.

Example:

http
Referer: https://example.com/account/settings?token=SECRET

This can leak:

Referrer-Policy controls how much information is sent.

Common values:

ValueBehavior
no-referrerNever send Referer
no-referrer-when-downgradeDefault in many browsers. No referrer when going HTTPS → HTTP
originSend only origin, for example https://example.com
origin-when-cross-originFull URL on same origin, only origin to other sites
strict-originOnly origin, and only for HTTPS → HTTPS
strict-origin-when-cross-originGood balanced default in modern apps

A safe and common choice:

http
Referrer-Policy: strict-origin-when-cross-origin

This:

Example: Response with referrer policy

http
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Referrer-Policy: strict-origin-when-cross-origin

Permissions-Policy

Modern browsers have many powerful features:

Permissions-Policy lets you control which origins can use these features. It can limit them for your own pages and for any iframes.

The syntax is:

http
Permissions-Policy: feature1=(allowed-origins), feature2=(allowed-origins)

Examples for allowed-origins:

Use Permissions-Policy to turn off features you do not use.
Less available features mean less attack surface.

Example: Disable camera and microphone, allow geolocation only on same origin

http
Permissions-Policy: camera=(), microphone=(), geolocation=('self')

Example: Disable all powerful features you do not need

For a simple content site that does not need any of these:

http
Permissions-Policy: camera=(), microphone=(), geolocation=(), fullscreen=('self'), payment=()

You can adjust based on your app's needs.


Cross-Origin Security Headers

Modern browsers and apps often load resources from other origins. For example:

Several headers help protect cross origin interactions.

We will not go deep into CORS here, because it usually has its own topic, but we will connect it to security headers.

CORS (Access-Control-Allow-*)

CORS is controlled mainly with these headers:

They decide which other origins can access your resources with JavaScript.

For example, to allow a frontend at https://app.example.com to call your API:

http
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true

Avoid Access-Control-Allow-Origin: * for APIs that use cookies or credentials.
Always limit origins to the sites that should use your API.

Cross-Origin-Resource-Policy (CORP)

Cross-Origin-Resource-Policy controls who can load your resources.

Common values:

ValueMeaning
same-originOnly same origin sites can load resource
same-siteOnly same site (including subdomains)
cross-originAny origin can load

For many APIs or private resources, you can use:

http
Cross-Origin-Resource-Policy: same-site

For very strict protection:

http
Cross-Origin-Resource-Policy: same-origin

Cross-Origin-Opener-Policy (COOP)

Cross-Origin-Opener-Policy controls how new windows and tabs interact with the opener. It is part of an isolation model to prevent side channel attacks.

Common values:

For many modern apps you will see:

http
Cross-Origin-Opener-Policy: same-origin

This isolates your browsing context from others.

Cross-Origin-Embedder-Policy (COEP)

Cross-Origin-Embedder-Policy controls if your page can load cross origin resources that are not explicitly allowed. It is often used together with COOP for strong isolation.

Values:

For special use cases, like applications that use SharedArrayBuffer, you might see:

http
Cross-Origin-Embedder-Policy: require-corp

For basic backend development you mostly need to know that these exist and are related to advanced browser security models.


X-XSS-Protection

X-XSS-Protection was used to control old browser built-in XSS filters. Many modern browsers either ignore it or have removed these filters, because they sometimes caused other issues.

Common values:

Example:

http
X-XSS-Protection: 1; mode=block

Today:

For most new apps, focus more on:

How to Add Security Headers in a Backend

The exact way to add headers depends on the framework and language. The ideas are the same:

  1. Decide which headers you want and their values
  2. Add them in a central place, usually middleware or global configuration
  3. Test that they appear in responses and do not break your app

Below are simple examples using generic pseudo code and Python / FastAPI style, but the concept applies to any backend.

Generic middleware pattern

Most frameworks have a concept similar to this:

pseudo
function security_headers_middleware(request, next_handler):
    response = next_handler(request)
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "SAMEORIGIN"
    response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
    response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
    response.headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=('self')"
    return response

Then you register this middleware globally.

Example: FastAPI style middleware

If you use FastAPI, you could write:

python
from fastapi import FastAPI, Request
from fastapi.responses import Response
app = FastAPI()
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
    response: Response = await call_next(request)
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "SAMEORIGIN"
    response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
    response.headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=('self')"
    # Only add HSTS if using HTTPS in production
    # In development with HTTP, skip this header
    response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
    return response

You can customize CSP per route or apply a default:

python
CSP = "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';"
@app.middleware("http")
async def add_csp(request: Request, call_next):
    response = await call_next(request)
    response.headers["Content-Security-Policy"] = CSP
    return response

You will adapt this idea to your own framework: Express.js, Django, Laravel, Spring, etc.


Recommended Default Set

For a typical modern HTTPS backend with a browser based frontend, a common baseline is:

http
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=31536000; includeSubDomains
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=('self')
Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self';
Cross-Origin-Resource-Policy: same-site

You might need to adjust:

Always test new security headers in a staging environment first.
A wrong CSP or CORS configuration can break your frontend.


Testing and Tools

To check your security headers:

  1. Open browser dev tools, go to the "Network" tab
  2. Reload your page
  3. Click a request and look at the "Response Headers" section

You can also use online scanners:

They will:

By understanding and correctly using security headers, you give your backend an extra protective layer. Even small config changes, like adding X-Content-Type-Options: nosniff or X-Frame-Options: SAMEORIGIN, can block entire classes of attacks with almost no code changes.

Views: 8

Comments

Please login to add a comment.

Don't have an account? Register now!