# Clickjacking explained: why frame-ancestors is on every security checklist

> Clickjacking loads your site in an invisible iframe so victims click your buttons without knowing. The fix is the CSP frame-ancestors directive sent as an HTTP header, with X-Frame-Options as a fallback for older browsers.

- Canonical: https://info.centralcsp.com/articles/clickjacking-explained/
- Published: 2026-08-09
- Language: en
- Publisher: CentralCSP (https://centralcsp.com)

**Clickjacking** is an attack where a malicious page loads your website inside an invisible iframe and paints its own content over it. The victim thinks they are clicking a harmless button on the attacker's page. The click actually lands on your real page underneath, with their real session cookies attached. The defense is to forbid framing with the CSP directive `frame-ancestors 'none'` (or `'self'`), sent as an HTTP header, plus `X-Frame-Options` for older browsers.

## What a clickjacking attack looks like

Picture the attacker's page: a bright "You won, claim your prize" button in the middle of the screen. Behind it, loaded in an iframe with `opacity: 0`, sits your application, scrolled and positioned so that your "Delete account" or "Confirm transfer" button lines up exactly under the prize button. The victim is logged in to your site in the same browser, so the iframe renders their authenticated session.

They click the prize. The browser delivers the click to your button, through the transparent layer, with all the cookies a legitimate click would carry. No malware, no stolen password, no exploit code. Just CSS and an iframe.

## Why browsers allow this at all

Iframes exist to compose interfaces across origins. Payment forms, embedded videos, support chat widgets, map tiles: the modern web assumes one page can embed another. So by default the browser will happily render your site, authenticated session included, inside anyone else's page. It has no way to know the embedding is hostile unless you tell it who is allowed to frame you.

Not every page is a good target. The attack needs an action a logged-in user can trigger with a single click and no typing: delete account, approve a transfer, change a recovery email, authorize an OAuth grant, one-click purchase. If your application has any of those (and almost every application does), framing protection belongs on it.

## The fix: the frame-ancestors CSP directive

`frame-ancestors` tells the browser which origins may embed the page, and it checks the entire ancestor chain, not just the direct parent:

```http
Content-Security-Policy: frame-ancestors 'none'
```

`'none'` forbids all framing. `'self'` allows your own origin only, which keeps internal embedding working. And unlike every older mechanism, `frame-ancestors` takes a real allowlist, so legitimate embedding partners are a supported case rather than a hack:

```http
Content-Security-Policy: frame-ancestors 'self' https://partner.example
```

That allowlist is the reason this directive is the modern control. Widget vendors and white-label products need exactly this: framed by three known customers, by nobody else.

## X-Frame-Options vs frame-ancestors

`X-Frame-Options` predates CSP and only knows two usable values: `DENY` and `SAMEORIGIN`. The `ALLOW-FROM` value is dead: it never worked cross-browser and current browsers ignore it, so if you need an allowlist, `frame-ancestors` is the only option. When both headers are present, browsers that support `frame-ancestors` (all current ones) use it and ignore `X-Frame-Options`.

The practical recipe has not changed in years: ship `frame-ancestors` as the control that matters, keep `X-Frame-Options: DENY` (or `SAMEORIGIN`) alongside it for whatever legacy browsers still visit you, and delete any `ALLOW-FROM` you find in old configs. The longer version of this comparison, including edge cases, lives in the [main site's deep dive on frame-ancestors and X-Frame-Options](https://centralcsp.com/en/blog/x-frame-options-vs-frame-ancestors).

One thing to decide deliberately: `SAMEORIGIN` and `frame-ancestors 'self'` are the comfortable defaults, but check first whether marketing embeds your signup page somewhere, or a partner iframes your checkout. Breaking a revenue-generating embed is how framing protection gets reverted in a hurry.

## Why it must be an HTTP header, never a meta tag

CSP can be delivered through a `<meta http-equiv>` tag, but the spec explicitly ignores `frame-ancestors` there. The framing decision has to be made before the document renders inside the frame, and a meta tag arrives too late. Teams add the meta tag, see no error, and ship a protection that does nothing. Set it in the server response header (or at the CDN or reverse proxy) or it does not exist.

## The cheapest pentest finding you will ever close

Missing framing protection shows up in nearly every scanner run and pentest report, right next to missing HSTS. It is flagged so often because it is trivial to test for and genuinely common. It is also about the cheapest finding to fix: two response headers, no application code, no user-visible change unless someone was framing you.

To check your own site, the free [CentralCSP scanner](https://centralcsp.com/en/tools/csp-scanner/) scans a URL without an account and flags missing framing protection along with the rest of the policy. And once `frame-ancestors` ships with `report-to` configured, violation reports tell you who is actually attempting to frame your pages in the wild, which turns a checklist item into a small detection signal (the delivery mechanics are covered in [report-uri vs report-to vs Reporting-Endpoints](/articles/report-uri-vs-report-to-vs-reporting-endpoints/)).

Clickjacking is one slice of the wider client-side problem: what runs and renders in your users' browsers is your attack surface too. For the map of that territory, start with [what is client-side security](/articles/what-is-client-side-security/), and if CSP itself is new to you, [what is a Content Security Policy](/articles/what-is-a-content-security-policy/) covers the foundations.

## Frequently asked questions

### What is clickjacking in simple terms?

Clickjacking is an attack where a malicious page loads your website in an invisible iframe and places fake content on top of it. The victim believes they are clicking a button on the attacker page, but the click lands on your real page underneath, on a logged-in session. It is stopped by forbidding other sites from framing yours, with the CSP frame-ancestors directive.

### Do I still need X-Frame-Options if I set frame-ancestors?

Keep both for now. Every current browser honors frame-ancestors and ignores X-Frame-Options when both are present, so the CSP directive is the one doing the work. X-Frame-Options DENY or SAMEORIGIN only covers legacy browsers that predate CSP Level 2. ALLOW-FROM is dead and should never be used.

### Can I set frame-ancestors in a meta tag?

No. The CSP specification explicitly ignores frame-ancestors when the policy is delivered through a meta tag. The browser needs the framing decision before it starts rendering the document, so the directive only works in the Content-Security-Policy HTTP response header.

### My pentest report flagged missing clickjacking protection. How serious is it?

Severity depends on what a single click can do on your site. If authenticated users can delete data, confirm payments or grant access with one click, treat it as a real finding. Either way it is one of the cheapest fixes in the report: add frame-ancestors to your CSP header and X-Frame-Options for older browsers, one line each.
