Web Security

What Is Clickjacking and How to Prevent It

Clickjacking tricks users into clicking hidden controls via invisible frames. Learn how to check if your site is framable and the defences that stop it.

StackOptic Research Team19 May 20269 min read
What clickjacking is and how to prevent it

Clickjacking is an attack that turns your own trusted page into a trap, not by breaking into it, but by tricking your users into clicking things they cannot see. In plain terms, clickjacking loads your real page inside an invisible frame and layers deceptive content on top, so a user who thinks they are clicking the attacker's page is actually clicking yours — confirming an action, changing a setting, or granting a permission without realising it. The good news for site owners is that the defence is straightforward and almost entirely free: tell browsers your pages may not be framed by sites you do not trust. This guide explains what clickjacking is, how to check whether your site is exposed, and exactly how to prevent it.

It is part of the broader how to protect your website from common attacks picture, and the headers it relies on are covered in HTTP security headers explained.

What clickjacking actually is

Clickjacking is short for "click hijacking", and it belongs to a family of attacks sometimes called UI redress — because the attacker redresses, or disguises, the user interface. The OWASP project describes it as an interface-based attack in which a user is tricked into clicking actionable content on a hidden page.

The core idea is deception about what is being clicked. The attacker builds a page that looks innocent — a game, a video, a "claim your prize" button. Hidden within it, the attacker also loads a genuine page from a site the victim is logged into. Through styling, the genuine page is made invisible or is precisely positioned so that an enticing decoy button sits exactly over a meaningful control on the real page. When the victim clicks the decoy, the click actually registers on the hidden real control. Because the victim is already authenticated to the real site in their browser, the action is performed as them.

Crucially, the attacker never sees or steals anything directly through the frame — modern browsers prevent a page on one origin from reading the contents of a framed page on another origin. What clickjacking exploits is simpler and sneakier: the user's own authenticated session and their misplaced trust in what their click will do. That is why the defence is about preventing the framing, not about hiding data.

A concrete, harmless illustration

To keep this strictly defensive, here is the concept without any working attack code. Imagine a settings page on a web app where a single button toggles a sensitive option — say, making a profile public. Now imagine an attacker's page that displays a tempting "Play" button. The attacker has arranged, behind the scenes, for the app's settings page to be present but invisible, positioned so the real "Make public" button sits precisely under the visible "Play" button. The victim sees only "Play", clicks it, and unknowingly toggles their profile to public on the real app.

That is the entire shape of the attack: perceived click on the decoy, actual click on the hidden control. Variants include "likejacking" (hijacking social-media like buttons) and tricking users through dragging or multi-step interactions, but they all rest on the same foundation — your page being embedded somewhere it should not be. Remove the ability to frame your page from untrusted origins, and the attack has nothing to stand on.

Why framing is the root cause

Every clickjacking attack depends on one prerequisite: the attacker must be able to load your page inside a frame on a page they control (typically an <iframe>). If your site refuses to render inside frames on origins you have not approved, the attacker cannot construct the overlay in the first place. This is why the primary defences are all about controlling who may frame you, rather than about the clicks themselves.

There are two headers for this, and a modern site should understand both.

Defence 1: CSP frame-ancestors (the modern standard)

The current, recommended control is the frame-ancestors directive of the Content-Security-Policy header. It tells the browser exactly which origins, if any, are allowed to embed your page in a frame, iframe, object or embed. It supersedes the older X-Frame-Options header and is more flexible because it can name a list of permitted origins.

The common settings are:

  • frame-ancestors 'none' — no site may frame your page at all, not even you. This is the most restrictive and a sensible default for pages that are never meant to be embedded (login pages, account settings, checkout, admin).
  • frame-ancestors 'self' — only pages on your own origin may frame the page. Use this when your own application legitimately embeds its own pages.
  • frame-ancestors 'self' https://partner.example.com — your origin plus specific, named trusted origins. Use this for legitimate embedding, such as a widget you deliberately allow a partner to host.

You would deliver it as part of your CSP response header, for example a policy that includes frame-ancestors 'none'. Because it is part of CSP, it fits naturally alongside the other protections in what is a Content Security Policy and how to set one — and unlike most of CSP, frame-ancestors rarely breaks anything, so it is one of the safest pieces of a policy to deploy first.

Defence 2: X-Frame-Options (the legacy fallback)

Before frame-ancestors existed, the way to prevent framing was the X-Frame-Options response header. It is older and less flexible, but still worth setting as a fallback for older browsers that predate frame-ancestors support. It accepts:

  • DENY — no site may frame the page (equivalent in spirit to frame-ancestors 'none').
  • SAMEORIGIN — only your own origin may frame the page (equivalent to frame-ancestors 'self').
  • ALLOW-FROM <origin> — historically allowed one specific origin, but this value is inconsistently supported and effectively deprecated, so do not rely on it; use frame-ancestors for allow-listing instead.

The modern best practice is to set both: frame-ancestors as the authoritative control, and X-Frame-Options (DENY or SAMEORIGIN) as a belt-and-braces fallback. Where the two are both present, current browsers that support CSP will honour frame-ancestors. Mozilla's MDN documentation describes X-Frame-Options as largely superseded by frame-ancestors but still useful for older clients.

Defence comparison: which control does what

DefenceHow it stops clickjackingNotes
CSP frame-ancestors 'none'Browser refuses to render the page in any frameModern standard; most flexible; preferred primary control
CSP frame-ancestors 'self'Allows framing only by your own originUse when your app embeds its own pages
CSP frame-ancestors + originsAllows framing only by named trusted originsFor legitimate partner/widget embedding
X-Frame-Options: DENYBlocks all framing in older browsersLegacy fallback; set alongside frame-ancestors
X-Frame-Options: SAMEORIGINAllows same-origin framing in older browsersLegacy fallback equivalent of 'self'
SameSite cookiesLimits what a framed authenticated session can doDefence in depth, not a primary control
Avoid sensitive one-click actionsReduces damage if a page is ever framedDesign-level mitigation

Frame-busting scripts: a weak, dated approach

Before headers existed, developers used frame-busting (or "frame-killing") JavaScript — a small script that detected when the page was inside a frame and tried to break itself out, for example by forcing itself to the top window. You may still see this in older codebases.

The honest assessment: frame-busting scripts are a fragile, last-resort measure, not a real defence. They can be defeated by various techniques, they fail entirely if the user has JavaScript disabled, and they have historically been bypassed in ways that are well documented. Treat any legacy frame-busting script as a hint that the page needs proper headers, not as protection in itself. The header-based approach (frame-ancestors plus X-Frame-Options) is enforced by the browser itself, before your page even runs, which is exactly why it is robust where a script is not.

Defence in depth: cookies and sensitive actions

While anti-framing headers are the core fix, two further measures reduce risk and limit damage.

SameSite cookies. Because clickjacking abuses the victim's existing authenticated session, limiting how that session behaves across sites helps. Setting your session cookies to SameSite=Lax or SameSite=Strict constrains when they are sent on cross-site requests, which overlaps with cross-site request forgery defences and can blunt some clickjacking variants. This is genuinely worth doing, but treat it as a supporting layer — the canonical guidance lives in how to secure cookies with HttpOnly, Secure and SameSite, and it does not replace frame-ancestors.

Avoid sensitive one-click actions. If a single click can perform a consequential, irreversible action, that action is a more attractive clickjacking target. Requiring an explicit confirmation step, re-authentication for sensitive operations, or a deliberate multi-step flow means a single hijacked click is far less likely to cause real harm. This is sound design regardless of clickjacking, and it provides a useful backstop.

How to check whether your site is framable

You can verify your exposure in a couple of minutes, without any special tooling.

1. Inspect the response headers. Open your browser's DevTools, go to the Network tab, click the main document request, and read the response headers. Look for Content-Security-Policy containing a frame-ancestors directive, and for X-Frame-Options. From a terminal, curl -I https://example.com prints the headers so you can see at a glance whether either is present. No relevant header means your pages can likely be framed by anyone.

2. Try to frame it on a test page you control. On an origin you own (even a local file), create a minimal HTML page containing an <iframe> whose src points at your site, and open it. If your anti-framing headers are working, the browser will refuse to render your page in the frame (and usually log a message in the console explaining why). If your page loads happily inside the frame, it is framable and needs the headers above. This is a legitimate, defensive self-test against your own site.

3. Use a header grader. Online security-header checkers and broader audits will flag a missing anti-framing policy. StackOptic, for instance, reports the presence of clickjacking-relevant headers as part of a wider security-header grade, alongside performance and SEO, so you can spot the gap in context rather than testing in isolation. For the full set of headers worth checking, see HTTP security headers explained.

Common mistakes

  • Relying on a frame-busting script instead of headers — it is bypassable and fails without JavaScript.
  • Setting only X-Frame-Options and omitting frame-ancestors — you miss the modern standard; set both.
  • Trusting the deprecated ALLOW-FROM value — it is inconsistently supported; use frame-ancestors to allow specific origins.
  • Protecting the homepage but not sensitive pages — login, settings, checkout and admin pages are the real targets; ensure the headers apply site-wide.
  • Allowing framing too broadlyframe-ancestors * or a long allow-list defeats the purpose; start with 'none' or 'self' and add origins only where you genuinely embed.
  • Forgetting the headers on error pages and sub-apps — anti-framing protection must cover every response, not just the main templates.

A quick clickjacking checklist

  • Set Content-Security-Policy: frame-ancestors 'none' (or 'self', or named trusted origins) as your primary control.
  • Add X-Frame-Options: DENY (or SAMEORIGIN) as a legacy fallback.
  • Apply both site-wide, including login, settings, checkout, admin and error pages.
  • Set session cookies to SameSite=Lax or Strict for defence in depth.
  • Require confirmation or re-authentication for sensitive, irreversible actions.
  • Self-test by inspecting headers and trying to frame your site on a page you control.
  • Remove any legacy frame-busting scripts once proper headers are in place.

Go deeper

Want to know whether your site is framable and which security headers are missing? Analyse any URL with StackOptic — security, performance and SEO in one free report.

Frequently asked questions

What is clickjacking?

Clickjacking, also called a UI redress attack, is a technique that tricks a user into clicking something other than what they believe they are clicking. The attacker loads a legitimate page, often inside an invisible or disguised frame, and overlays it with decoy content. The victim thinks they are interacting with the attacker's harmless-looking page, but their clicks actually land on the hidden real page, performing actions like changing a setting or confirming a request without their knowledge.

How do I prevent clickjacking on my website?

Prevent it by telling browsers your pages may not be framed by untrusted sites. Set the Content-Security-Policy frame-ancestors directive, the modern standard, to 'none' or 'self' (or list trusted origins), and add the legacy X-Frame-Options header as a fallback for older browsers. These headers stop your page rendering inside a malicious frame, which removes the foundation the attack relies on. Combine them with SameSite cookies for defence in depth.

What is the difference between X-Frame-Options and CSP frame-ancestors?

Both control whether your page can be embedded in a frame, but frame-ancestors is the modern, more flexible successor. X-Frame-Options only supports DENY or SAMEORIGIN (and a poorly supported ALLOW-FROM), while the Content-Security-Policy frame-ancestors directive can allow a list of specific origins and is the current standard. The recommended approach is to set frame-ancestors as the primary control and keep X-Frame-Options as a fallback for legacy browsers.

How can I check if my site can be framed?

Inspect your page's response headers for Content-Security-Policy with a frame-ancestors directive, or an X-Frame-Options header. From a terminal, curl -I https://example.com shows them. You can also create a simple test page on an origin you control with an iframe pointing at your site: if the page refuses to render in the frame, your anti-framing headers are working; if it loads, your site is framable and needs protection.

Do SameSite cookies stop clickjacking?

Not on their own, but they help as a defence-in-depth layer. Clickjacking abuses the fact that a framed page runs in the victim's authenticated session. SameSite cookies limit when cookies are sent on cross-site requests, which can reduce what a framed session is able to do and overlaps with cross-site request forgery defences. The primary clickjacking defence is still frame-ancestors and X-Frame-Options to prevent the framing itself.

Analyse any website with StackOptic

Get the full technology stack, performance, security and SEO report in seconds — free.

Analyse a website

Related articles