Glossary

X-Frame-Options

`X-Frame-Options` tells a browser whether a page may be displayed inside a frame on another site. Its purpose is to prevent clickjacking: an attacker embedding your page invisibly over their own and tricking a visitor into clicking something they cannot see. It has two usable values, `DENY` and `SAMEORIGIN`, and it has been superseded by the Content Security Policy `frame-ancestors` directive, which does the same job with more control.

The values

`DENY` — no site may frame this page, including the site itself.

`SAMEORIGIN` — only pages from the same origin may frame it.

`ALLOW-FROM <origin>` was defined to permit one named origin and is not supported by current browsers. A configuration using it provides no protection at all, which is worse than no header because it looks like protection.

There is no value permitting a list of origins. That is the capability `frame-ancestors` adds.

`SAMEORIGIN` is checked against the top-level document's origin in current browsers, not against the immediate parent frame, which closes a nesting gap older implementations had.

What frame-ancestors adds

`Content-Security-Policy: frame-ancestors 'none'` is equivalent to `DENY`, and `'self'` is equivalent to `SAMEORIGIN`.

It additionally accepts a list of origins, with wildcards, which is the case `ALLOW-FROM` failed to cover.

Where both are present, browsers that support `frame-ancestors` use it and ignore `X-Frame-Options`.

The practical advice is to set `frame-ancestors` and keep `X-Frame-Options` alongside it for any client that predates CSP support, at the cost of one extra header.

Which pages need it

Anything a visitor is authenticated on, because clickjacking works by borrowing the visitor's session to perform an action they intended to perform somewhere else.

Administrative interfaces, account settings, payment confirmations — anywhere a single click has a consequence.

Public marketing pages have far less to lose, though a site-wide `SAMEORIGIN` costs nothing and avoids the question.

Pages designed to be embedded — a widget, an embeddable player — must not set it, and `frame-ancestors` with an explicit list is the right tool there.

How to check it

`curl -I https://example.com | grep -iE 'x-frame-options|content-security-policy'` shows both.

Test on an authenticated page as well as the home page. Header configuration applied per route is the usual reason one is protected and another is not.

Attempting to frame the page from a local HTML file confirms the behaviour directly, and the console names the header that refused.

A header set in an HTML meta tag rather than as a real response header is ignored: `X-Frame-Options` only works as a header.

Check error pages and redirects too. A 404 or a login redirect served without the header is still a page an attacker can frame, and those responses frequently bypass the rule that adds headers to normal ones.

Frequently asked questions

Should I use X-Frame-Options or frame-ancestors?
frame-ancestors, which supersedes it and accepts a list of origins. Keeping X-Frame-Options alongside costs one header and covers older clients.
Why does ALLOW-FROM not work?
It was never implemented consistently and is unsupported by current browsers. A configuration using it provides no protection.
Can X-Frame-Options be set in a meta tag?
No. It is only honoured as an HTTP response header.

Sources

Related

VeriFixScan crawls a site and applies its checks to every page it reaches, keeping the evidence behind each finding. Scanning one website is free.

Scan a website