Skip to content
← All articles

Clickjacking Prevention: X-Frame-Options vs frame-ancestors

Clickjacking Prevention: X-Frame-Options vs frame-ancestors

Clickjacking — also known as a UI redress attack — is when an attacker iframes your site transparently over their own UI and tricks the user into clicking "invisible" buttons: confirm a payment, reset a password, grant a permission. Clickjacking mitigation is one of the cheapest security fixes in existence, yet around 40% of sites still miss it. Let's walk through the attack and three defensive layers: X-Frame-Options, CSP frame-ancestors, and JS frame-busting.

How clickjacking works

The attacker creates a page with a transparent iframe of your site on top of fake UI. The user thinks they're clicking "Claim prize" but actually clicks "Delete account" in their authenticated session on your site. The canonical example is the 2009 Twitter clickjacking worm that forced one-click retweets of malicious content. Reference: OWASP Clickjacking.

X-Frame-Options (legacy but universal)

X-Frame-Options is defined in RFC 7034. Three values:

X-Frame-Options: DENY

CSP frame-ancestors (modern standard)

The frame-ancestors directive in CSP Level 2+ replaces X-Frame-Options and supports multiple sources:

Content-Security-Policy: frame-ancestors 'none';
# or
Content-Security-Policy: frame-ancestors 'self' https://trusted-partner.com;

When both are present the browser honours CSP. Keep both for legacy clients.

SameSite cookies

Even if clickjacking succeeds, a cookie with SameSite=Lax or SameSite=Strict will not ride along on the cross-site iframe request — a second line of defence. Details in Cookie Security.

Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Strict

JS frame-busting (do not rely on this alone)

if (self !== top) {
  top.location = self.location;
}

This snippet breaks against the iframe sandbox attribute. Use it only as a fallback for browsers without CSP support.

Configuration

nginx:

add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none'" always;

Apache:

Header always set X-Frame-Options "DENY"
Header always set Content-Security-Policy "frame-ancestors 'none'"

Express/NestJS:

import helmet from 'helmet';
app.use(helmet.frameguard({ action: 'deny' }));
app.use(helmet.contentSecurityPolicy({
  directives: { frameAncestors: ["'none'"] }
}));

When SAMEORIGIN is appropriate

If you legitimately iframe across subdomains (admin widgets, embedded apps), use SAMEORIGIN or frame-ancestors 'self'. Never use ALLOWALL — it's equivalent to no protection.

Verification

curl -I https://example.com | grep -E "X-Frame-Options|Content-Security"

Or use the enterno.io Security Scanner, which actually tries to iframe your site and reports whether framing is blocked. Broader overview: HTTP Security Headers.

FAQ

Do I still need X-Frame-Options with CSP? Yes, for browsers older than CSP Level 2 (IE 11) and as defence-in-depth.

Can I allow framing only from one domain? Only via frame-ancestors https://partner.com; X-Frame-Options ALLOW-FROM is dead.

How strong is frame-busting JS? Weak — attackers bypass via iframe sandbox or HTML5 double-framing.

What about mobile WebViews? WebViews don't always honour CSP; add server-side Referer/Origin validation as a backup.

Conclusion

Minimum: ship two headers and call it done. Monitor via enterno monitors so any regression in config surfaces immediately. See also: Cookie Security, all security headers.

Check your website right now

Check now →
More articles: SEC
SEC
CSP (Content Security Policy): Setup Guide
15.04.2026 · 4 views
SEC
Cookie Security: HttpOnly, Secure, SameSite, __Host-
15.04.2026 · 6 views
SEC
Prevent XSS Attacks: Escaping, CSP and Trusted Types
15.04.2026 · 3 views
SEC
WAF (Web Application Firewall): A Practical Guide
15.04.2026 · 4 views