Clickjacking
Why it matters If any site may show your page inside a frame, it can hide your page under its own and trick visitors into clicking your buttons: "delete account", "share my location", "send money". The visitor is really logged in to your site, so the click is real. One header stops it.
Picture a sheet of glass with a painted "Claim your prize" button, laid over a real bank form so that the painted button sits exactly on top of "Confirm transfer". You press the prize; your finger goes through the glass. That is clickjacking.
Clickjacking: an attack where a page loads another site in an invisible or disguised frame (an <iframe>) and lines up that site's buttons under its own, so the visitor's clicks land on the hidden page.
Framing: one page showing another inside an iframe. The page inside is the framed page; the page around it is its ancestor.
In the practice world, prize.example is the attacker page and bank.example/transfer is the target. The defence belongs to the bank: it must tell browsers who may frame its pages. The visitor cannot see the trick, and the attacker will not stop by itself.
The modern way: frame-ancestors
Content-Security-Policy: frame-ancestors 'none' nobody may frame this page
Content-Security-Policy: frame-ancestors 'self' only pages from this same origin
Content-Security-Policy: frame-ancestors https://learn.floati.life only that one site- It checks every ancestor, not only the direct parent, so an attacker cannot wrap a friendly page around yours.
- It does not fall back to
default-src. A policy without it does not restrict framing at all. - It works only as a header, not in a
<meta>tag.
The old way: X-Frame-Options
X-Frame-Options: an older, separate header with two useful values, DENY (like 'none') and SAMEORIGIN (like 'self').
Why is it "the old way"? It cannot name another site: its ALLOW-FROM value was never supported everywhere and modern browsers ignore it. It was never part of CSP, so it has no report-only mode. And when a page sends both, browsers that understand frame-ancestors follow it and ignore X-Frame-Options. Many sites still send X-Frame-Options as well, as a backup for very old browsers. That is harmless.
A real example: this site
floati.life sends X-Frame-Options: DENY on every page, through Django's clickjacking middleware (checked October 2026). One page is the exception: a small "bridge" page that learn.floati.life loads in a hidden frame to carry a learner's progress across when the course moved. X-Frame-Options cannot say "only learn.floati.life", so that one view is exempted from it and sends frame-ancestors https://learn.floati.life instead. That is exactly the case the old header cannot handle.
@xframe_options_exempt
def bridge(request):
response = render(request, "bridge.html")
response._csp_update = {"frame-ancestors": [settings.LEARN_ORIGIN]}
return responseBelt and braces
Framing control is the real fix. Two further habits help: ask for confirmation, or a password, before very sensitive actions, and set session cookies to SameSite=Lax or Strict (Lesson 10), so a framed page from another site often is not logged in at all.
A common misconception: "my page has no secret data, so framing does not matter". Clickjacking is about stealing clicks, not reading data. Any button that changes something is a target.
Try it In DevTools, check the response headers of a site you use for x-frame-options and for frame-ancestors inside content-security-policy. Then think of its most important button: could a hidden frame aim at it?
Recap
- Clickjacking hides your page in a frame and steals real clicks from logged-in visitors.
- Send
frame-ancestors 'none'(or'self', or named sites); it checks every ancestor and never falls back todefault-src. X-Frame-Optionsis the old way: it cannot name another site and is ignored whenframe-ancestorsis present.
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.