---
title: Clickjacking Explained: how an invisible frame steals a click
description: Any website can load yours inside an invisible frame and let someone click your buttons while they think they're clicking something else. One header stops it, and almost nothing can go wrong by adding it.
canonical: https://watchfor.io/blog/clickjacking-explained
---

[All posts](/blog) [Security](/blog/category/security) Sep 08, 2026 · 4 min read · WatchFor Team

# Clickjacking Explained: how an invisible frame steals a click

Any website can load yours inside an invisible frame and let someone click your buttons while they think they're clicking something else. One header stops it, and almost nothing can go wrong by adding it.

Your site can be loaded inside another website. Not linked to — loaded , in a frame, on a page you've never seen, by someone you've never met. That's the default, and it has been since frames were invented.

Usually nothing comes of it. Occasionally someone builds a page whose only purpose is to make you click a button in your app while you believe you're clicking something in theirs . That's clickjacking, and the fix is one line.

## How the trick works

The attacker builds an ordinary-looking page — a prize wheel, a video player, a "click to continue" button. Then:

- They load your site in an <iframe> on top of their page.

- They make the frame invisible: opacity: 0 .

- They line up their bait button with the real button in your interface — "Delete account", "Approve payment", "Grant access", "Transfer".

- The visitor clicks the thing they can see. The click lands on the thing they can't.

The crucial part: the visitor is logged in to your site already. The browser sends their cookies with the frame like it would with any other visit, so as far as your server is concerned, they really did click it. Because they really did.

Nothing is exploited here. No bug, no injection, no stolen password. Two entirely normal features — frames and cookies — are simply pointed at each other.

## Why it isn't obvious you're exposed

There's nothing to see. Your site works. Your logs look normal — the requests come from real, logged-in users doing real, valid actions. The only trace is that the users didn't mean to do them, and that isn't a field you can query.

This is why the check appears in every [website audit](/smart-website-audit) even though everything else is green. Frameability is not a malfunction; it's a setting you haven't chosen yet.

## The fix, in one line

Two headers do the job. Send whichever you can, or both:

X-Frame-Options: SAMEORIGIN

Content-Security-Policy: frame-ancestors 'self'

X-Frame-Options is the old one, understood everywhere. frame-ancestors is part of [CSP](/blog/content-security-policy-explained), is more flexible, and wins where both are present in modern browsers. Sending both is normal and costs nothing.

Value Meaning

X-Frame-Options: DENY Nobody may frame this page, including you

X-Frame-Options: SAMEORIGIN Only pages on your own domain may frame it

frame-ancestors 'none' The CSP equivalent of DENY

frame-ancestors 'self' The CSP equivalent of SAMEORIGIN

frame-ancestors 'self' https://partner.example Your own pages, plus one named partner

ALLOW-FROM doesn't work. You'll find it in old blog posts and old configs. No current browser supports it. If you need to allow a specific partner, that's frame-ancestors in a CSP — the only mechanism that actually does it.

## What could break

Genuinely very little, which is why this is the easiest security header to ship. Ask two questions:

- Does anything embed our pages on purpose? Documentation widgets, an embedded checkout, a partner portal, a status page dropped into someone's intranet, an OAuth consent screen. If yes, name those origins in frame-ancestors rather than sending DENY .

- Do we embed ourselves? An admin panel that loads a preview of a public page, for example. SAMEORIGIN covers that; DENY does not.

If neither applies — the case for most sites — SAMEORIGIN is safe and you're done.

## The parts a header can't fix

- It only protects framing. A page that isn't framed at all — a phishing copy of your login screen on a lookalike domain — is a different problem.

- It doesn't help with target=_blank links or pop-ups. Different mechanism, different mitigation.

- Destructive actions still deserve a confirmation step. A header stops the invisible frame; a "type DELETE to confirm" step stops the ordinary mis-click too. Defence in depth means both.

## Keeping it there

Like every response header, this one lives in a config file that gets rewritten — a new reverse proxy, a framework upgrade, a CDN rule that strips unknown headers. Nothing breaks when it disappears, and nothing tells you.

The free [HTTP header checker](/http-header-checker) shows what your site sends right now, and the [Smart Website Checker](/smart-website-audit) reviews all six recommended headers in one scan. To catch a regression rather than discover it, an [API monitor](/docs/monitors/api) can assert the header on every check.

## The bottom line

In one line

What A header saying who may load your pages inside a frame.

Why Stops an invisible frame turning a visitor's click into an action in your app.

Danger without it Logged-in users perform real actions they never intended — and your logs agree with the attacker.

Add X-Frame-Options: SAMEORIGIN plus frame-ancestors 'self' .

Check first Whether anything legitimately embeds your pages.

Of all the hardening on the list, this is the one with the best ratio: two minutes of work, essentially no risk, and it closes an attack that needs no bug in your code at all.

Related: [Content Security Policy explained](/blog/content-security-policy-explained), [Referrer-Policy explained](/blog/referrer-policy-explained), [HSTS explained](/blog/hsts-explained).

[#security](/blog/tag/security)[#web](/blog/tag/web)

## Monitor your website & APIs

Continuous HTTP/HTTPS checks with status-code, latency and content assertions — plus instant alerts when something breaks.

[Learn more](/http-monitoring)[Start free](/auth/sign-up)

Share this article

---

Canonical page: https://watchfor.io/blog/clickjacking-explained · Site guide: https://watchfor.io/llms.txt
