Clickjacking & X-Frame-Options: stop your site being framed
What clickjacking actually is
Clickjacking is a trick of layering, not a breach of your server. An attacker loads your real, logged-in site inside an <iframe> on a page they control, then makes that frame transparent or hides it behind decoy content. The user sees the attacker's page and clicks what looks like a harmless button — but the click lands on your site underneath. If the user is authenticated, that click can do something real: confirm an action, change a setting, approve a request.
The defense is simple and one-time: tell the browser who, if anyone, is allowed to put your pages inside a frame.
The modern control: frame-ancestors
The current, preferred way is a directive inside your Content-Security-Policy header. To forbid framing entirely:
Content-Security-Policy: frame-ancestors 'none'
To allow only your own pages to frame your content (useful if you legitimately embed yourself):
Content-Security-Policy: frame-ancestors 'self'
And to allow a specific trusted partner:
Content-Security-Policy: frame-ancestors 'self' https://partner.example
frame-ancestors is the one to reach for first because it is part of CSP, supports an allowlist, and is honoured by current browsers.
The legacy fallback: X-Frame-Options
Before frame-ancestors, the header was X-Frame-Options. It is coarser — it does not support an allowlist of multiple origins — but it is still worth sending for older clients:
X-Frame-Options: DENYblocks all framing.X-Frame-Options: SAMEORIGINallows only your own origin to frame you.
Setting both frame-ancestors and X-Frame-Options is normal and sensible: modern browsers follow the CSP directive, older ones fall back to the legacy header. They do not conflict.
What value should you actually use?
For the vast majority of sites, the honest answer is do not allow framing at all. A typical marketing site, dashboard or checkout has no reason to be embedded by anyone, so frame-ancestors 'none' plus X-Frame-Options: DENY is the safe default. Only relax it if you have a real embedding use case — and then allowlist exactly the origins involved, nothing wildcard.
A quick word on what framing can do
It helps to picture the payload. Because the framed page is your real, authenticated site, the hijacked click does whatever a real click would: a Delete button, a Confirm payment, a Grant access toggle, a one-click subscribe. The attacker does not need your password or session — they borrow the session the browser already has and steer a single, well-placed click. That is why even ordinary action buttons are worth protecting, not just obviously sensitive ones.
Why it is so often missing
Frameworks do not set these headers for you. Unless someone deliberately added an anti-framing policy, your pages can be framed by anyone — which is why this shows up constantly in header checks. The fix is a single line of config, applied site-wide, and it costs you nothing in performance or compatibility for a site that is never meant to be embedded.
Check whether you can be framed
You cannot tell from the page whether your responses forbid framing — the header is invisible to the rendered site. An automated scan reads your actual responses and reports whether frame-ancestors or X-Frame-Options is present and correctly set. Scan your site and close the framing gap.