Important Headers 5 min read

Preventing Clickjacking with X-Frame-Options

Clickjacking tricks users into clicking something they can't see — an invisible iframe layered over a button they think they're clicking. X-Frame-Options and its modern replacement CSP frame-ancestors prevent your pages from being embedded in another site's frame.

What is Clickjacking?

In a clickjacking attack, the attacker creates a web page with an invisible <iframe> loaded from your site, positioned precisely over something on their own page. When the victim thinks they're clicking "Win a prize" on the attacker's page, they're actually clicking "Confirm wire transfer" on your bank's invisible page beneath it.

The attack works because the browser allows any website to embed another website in an iframe — unless the target site explicitly prevents it. The victim is authenticated on your site (their cookie is sent with the iframe request), so their actions within the invisible iframe have real consequences: money transfers, account deletions, settings changes, social media posts.

Clickjacking affects any site where authenticated users take high-value actions via a single click: banking apps, email clients, social networks, admin panels, e-commerce checkout buttons.

X-Frame-Options

The X-Frame-Options header tells the browser whether your page is allowed to be loaded inside a frame, iframe, or object element. It has two useful values:

DENY

The page cannot be displayed in a frame under any circumstances, even on the same site. Best for pages that should never be embedded — login pages, checkout pages, admin interfaces.

SAMEORIGIN

The page can only be framed by pages from the same origin (same scheme, host, and port). Use this if you legitimately embed your own pages in iframes on your own site.

A third value, ALLOW-FROM uri, allowed specific third-party origins. It is obsolete — it was never supported in Chrome or Safari and is now removed from the spec. Use CSP frame-ancestors for that use case.

How to set it

# Nginx
add_header X-Frame-Options "DENY" always;

# Apache
Header always set X-Frame-Options "DENY"

CSP frame-ancestors: The Modern Alternative

CSP's frame-ancestors directive does everything X-Frame-Options does, plus more. It supports multiple allowed origins, wildcards for subdomains, and is the standard recommended by modern security guidance.

# No framing at all
Content-Security-Policy: frame-ancestors 'none';

# Only same origin
Content-Security-Policy: frame-ancestors 'self';

# Allow specific trusted third party
Content-Security-Policy: frame-ancestors 'self' https://dashboard.partner.com;

When both X-Frame-Options and CSP frame-ancestors are present, frame-ancestors takes precedence in browsers that support it. Older browsers that don't understand CSP will fall back to X-Frame-Options.

Which One Should You Use?

Best practice: Set both headers for maximum compatibility. Use Content-Security-Policy: frame-ancestors 'none' as the primary control and X-Frame-Options: DENY as a fallback for legacy browsers.
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none';

If you need to allow your own site to embed the page (for legitimate iframes), use:

X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self';

WebAudit Scoring Note

WebAudit gives full credit (no deduction) if either X-Frame-Options is set correctly OR if CSP includes a frame-ancestors directive. You don't need both — they provide equivalent protection. Setting both is still recommended for defence in depth, but either one alone is sufficient to satisfy the check.

Browser Support

Common Mistakes

Is your site protected from clickjacking?

WebAudit checks for X-Frame-Options and CSP frame-ancestors and flags pages that are vulnerable to embedding attacks.

Check your site's security now →