X-Frame-Options

An attacker puts your page inside an iframe on their own site, makes it invisible, and lines up a button of theirs underneath a button of yours. The user clicks "claim prize." The browser actually delivers the click to your page, transferring money or revoking permissions or whatever the hidden button does.

That is clickjacking. X-Frame-Options is the oldest defence: a response header that tells the browser which origins are allowed to put your page in a frame.

The attack

A transparent iframe overlays a visible button. The click lands inside the iframe.

Without XFOclickjacked
attacker's page
Click to claim your prize
iframe: bank.com/transfer (opacity 0.01)
[ Confirm transfer of $1000 ]
The user clicks what looks like "claim prize." The invisible iframe catches the click and submits a transfer they never saw.
With XFO: DENYframe blocked
attacker's page
Click to claim your prize
iframe: bank.com/transfer
(empty: XFO: DENY from bank.com)
Browser requests the iframe, sees X-Frame-Options: DENY in the response, refuses to render it. The invisible hitbox is simply empty.

Nothing about this is exotic. The technique was documented in 2008, and enough high-value buttons (transfer, delete, share, revoke) still live on pages with no frame protection that clickjacking remains a working attack today.

The defence

Send one response header. The browser honours it before rendering inside any frame.

X-Frame-Options: DENY

When a page embeds your URL in a frame, the browser fetches the HTML, checks this header, and refuses to render if the value says so. The frame stays empty. The attacker cannot catch the click because there is nothing to click.

Defined in RFC 7034. The spec is Informational; the de-facto standard is what Chrome, Firefox, and Safari have agreed to implement.

The three values

X-Frame-Options valuesRFC 7034
DENYno origin may frame this page. The strongest setting.pass
SAMEORIGINonly same-origin pages may frame this one. Fine for most apps.pass
ALLOW-FROM originbrowsers ignore this entirely. Use CSP frame-ancestors instead.ignored

ALLOW-FROM was part of the original spec. No modern browser implements it: if your policy is "only example.com may frame me," ship CSP frame-ancestors instead. XFO with ALLOW-FROM is equivalent to no header at all in practice.

Modern replacement

CSP's frame-ancestors directive does what XFO does, with three advantages: it supports multiple origins, it supports wildcards, and it is not plagued by the ALLOW-FROM fragmentation.

Content-Security-Policy: frame-ancestors 'none'
Content-Security-Policy: frame-ancestors 'self' https://partner.example.com

When both headers are present and they disagree, browsers defer to CSP per the CSP Level 2 and 3 specs. The usual pattern in 2026 is to ship both: XFO for the long tail of older browsers that might still be out there, CSP for everyone else.

When framing is legitimate

Some pages actually want to be framed. Embeddable widgets (video players, payment widgets, OAuth consent screens, SSO interstitials) are designed to live inside another page. For those, set the allowlist precisely:

Content-Security-Policy: frame-ancestors 'self' https://checkout-partner.example.com

Never use SAMEORIGIN on a page that needs cross-origin embedding. It blocks the feature. Never use DENY either.

Common mistakes

no XFO and no frame-ancestors

Any origin can frame your pages. Worst case on authenticated actions (transfer, delete, admin toggle). Ship something.

ALLOW-FROM origin

Every modern browser ignores it. Behaves as "no protection." Use CSP frame-ancestors instead.

XFO set in a <meta> tag

Browsers check HTTP headers, not the document. XFO from a meta tag is ignored.

SAMEORIGIN on a page that must be embedded

SSO and payment screens are the usual victims. The page ships defaults for the app, nobody remembers to override them for the embeddable route, the vendor integration silently breaks.

XFO and frame-ancestors disagree

Inconsistent across edge configs and origin servers. Decide on one source of truth, set the other from it.


Check whether your pages ship XFO, whether they also ship CSP frame-ancestors, and whether the two agree: scan your domain.