How to Prevent Clickjacking? Complete Guide (2026)

Saar Twito6 min read
Saar Twito
Saar TwitoFounder & SEO Engineer

Hi, I'm Saar - a software engineer, SEO specialist, and lecturer who loves building tools and teaching tech.

View author profile →

What Is Clickjacking?

Clickjacking — also called UI redressing — is an attack where a malicious page embeds your site in a transparent <iframe>and overlays its own interface on top. The user thinks they are clicking a button on the attacker's page, but they are actually clicking a hidden element on your site: a "Confirm payment" button, a "Grant permission" dialog, or a social media "Like" button. The click is real; the user's intent is not.

The attack was popularized in 2008 by security researchers Robert Hansen and Jeremiah Grossman. Despite being over 15 years old, clickjacking remains relevant — the OWASP Clickjacking page documents it as an ongoing threat for applications that handle sensitive actions, and the OWASP Clickjacking Defense Cheat Sheet defines the canonical mitigation pattern used today.

Key Facts (TL;DR)

  • Attack type: UI redressing — the user clicks a hidden element on your page embedded in an attacker's iframe.
  • Primary defence: Content-Security-Policy: frame-ancestors 'none' (modern) plus X-Frame-Options: DENY (legacy fallback).
  • OWASP defence-in-depth: The OWASP Cheat Sheet defines three layers — frame-ancestors, X-Frame-Options, and SameSite cookies.
  • Standards: X-Frame-Options is documented in RFC 7034 (Informational, October 2013); frame-ancestors is part of W3C CSP Level 3.
  • Greadme audit: Flagged under Security when neither header is present or when framing is unrestricted.

How a Clickjacking Attack Works

The mechanics are straightforward. An attacker sets up a page at evil.example containing:

<!-- Attacker's page — your site is invisible but clickable -->
<style>
  iframe {
    opacity: 0;           /* invisible */
    position: absolute;
    top: 0; left: 0;
    width: 100%; height: 100%;
    z-index: 999;         /* on top of everything */
    pointer-events: all;
  }
  .decoy-button {
    position: absolute;
    top: 200px; left: 150px; /* aligned with your "Delete account" button */
  }
</style>

<iframe src="https://yoursite.com/account/settings"></iframe>
<button class="decoy-button">Click here to win a prize!</button>

The user sees the decoy button. When they click it, the click hits the invisible iframe — specifically your "Delete account" button — because the attacker has positioned the iframe so the two overlap. Your site processes the action as a legitimate authenticated click, because the user's session cookie is sent automatically with the iframe request.

X-Frame-Options: The Original Defence

The X-Frame-Options HTTP response header tells browsers whether the page is allowed to be embedded in a frame. It was first implemented by Microsoft in 2009 and later documented as IETF RFC 7034 (Informational, October 2013). See the MDN X-Frame-Options reference for current browser support. There are two practical values:

ValueMeaningUse when
DENYThe page cannot be embedded in any frame, regardless of originDefault choice — use on all pages that do not need to be framed
SAMEORIGINThe page can only be embedded in a frame on the same originWhen your own site embeds one of its own pages in an iframe
ALLOW-FROM uriThe page can only be embedded by the specified originObsolete — modern Chrome and Firefox ignore the entire header when this value is present, leaving you with zero clickjacking protection. Use frame-ancestors instead.
# Block all framing — recommended default
X-Frame-Options: DENY

# Allow framing only from the same origin
X-Frame-Options: SAMEORIGIN

CSP frame-ancestors: The Modern Standard

The frame-ancestors directive in a Content-Security-Policy header does the same job as X-Frame-Options but with more flexibility and better browser support. It was added in CSP Level 2 (which became a W3C Recommendation in December 2016) and is the modern standard. The CSP specification mandates: "If a resource is delivered with a policy that includes a directive named frame-ancestors and whose disposition is 'enforce', then the X-Frame-Options header MUST be ignored." In other words, when both headers are present, frame-ancestors takes precedence.

Note: frame-ancestors only works as an HTTP response header — it cannot be set via a <meta http-equiv> tag.

# Block all framing
Content-Security-Policy: frame-ancestors 'none';

# Allow framing only from the same origin
Content-Security-Policy: frame-ancestors 'self';

# Allow framing from specific trusted partners
Content-Security-Policy: frame-ancestors 'self' https://partner.example.com;

Unlike X-Frame-Options, frame-ancestors supports multiple origins and works correctly in all modern browsers. The deprecated ALLOW-FROM value in X-Frame-Options had inconsistent support — frame-ancestors with a specific origin replaces it reliably.

Which Header Should You Use?

Use both. They are not redundant — they serve different browser generations:

  • frame-ancestors (CSP) takes precedence in browsers that support it. It is the standard going forward.
  • X-Frame-Options acts as a fallback for older browsers that pre-date CSP Level 2 support.

The combination gives you complete coverage with no gaps:

# Send both headers for maximum browser coverage
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none';

Note: if your CSP header already contains other directives, append frame-ancestors 'none' to the existing policy rather than sending a second Content-Security-Policy header — multiple CSP headers are merged by the browser using the most restrictive policy.

SameSite Cookies: OWASP's Third Defence Layer

The OWASP Clickjacking Defense Cheat Sheet lists three independent layers of defence — frame-ancestors, X-Frame-Options, and SameSite session cookies. The SameSitecookie attribute prevents the browser from attaching the user's session cookie when the page is loaded inside a cross-origin iframe, which neutralises any authenticated action a clickjacking attack might trigger.

# Set SameSite on session cookies — recommended baseline
Set-Cookie: session_id=abc123; Secure; HttpOnly; SameSite=Lax

# Strictest — never sent in any cross-site context
Set-Cookie: session_id=abc123; Secure; HttpOnly; SameSite=Strict

Important caveat from OWASP: "If the clickjacking attack does not require the user to be authenticated, this attribute will not provide any protection." SameSite is necessary but not sufficient — combine it with frame-ancestors and X-Frame-Options for full coverage.

When You Legitimately Need Framing

Some features require your page to be embeddable — payment widgets, embeddable maps, social share buttons, or a widget your partners embed on their sites. In these cases DENY is too aggressive. The correct approach is to allow framing only from specific trusted origins using frame-ancestors:

# Allow embedding only by your own origin and one trusted partner
Content-Security-Policy: frame-ancestors 'self' https://trusted-partner.example.com;

Apply the restrictive DENY policy site-wide, and override it with a permissive frame-ancestors only on the specific routes that need to be embeddable. Most pages — account settings, checkout, admin panels — should never be frameable.

Common Clickjacking Mistakes

Relying on JavaScript frame-busting scripts

Before X-Frame-Options existed, developers used JavaScript to break out of frames — typically if (window.top !== window.self) window.top.location = window.location. Attackers bypass this trivially by using the sandbox attribute on the iframe to disable JavaScript. Frame-busting scripts are not a reliable defence and should not be used in place of HTTP headers.

Setting X-Frame-Options only on the homepage

Clickjacking attacks typically target specific action pages — settings, payment confirmation, password change — not the homepage. The header must be set on every response, not just the root URL. Apply it globally in your server or middleware configuration.

Using ALLOW-FROM

The ALLOW-FROM uri value for X-Frame-Options is not supported in Chrome or Firefox. If you need to allow framing from a specific origin, use Content-Security-Policy: frame-ancestors https://trusted.example.com instead.

Forgetting that HTTPS is required

A page served over HTTP can be framed by an attacker even with these headers set correctly, because an attacker on the same network can strip the headers using an SSL stripping attack. Always combine clickjacking protection with HSTS to ensure headers are delivered over a secure connection.

How to Verify Your Framing Protection

  • Chrome DevTools → Network → response headers — check that X-Frame-Options and/or Content-Security-Policy with frame-ancestors appear on the responses for your sensitive pages.
  • Quick manual test — create a local HTML file with an <iframe src="https://yoursite.com"> and open it in a browser. If your framing protection is working, the browser will block the iframe and log an error in the console.
  • Greadme deep scan — audits your security headers including X-Frame-Options and frame-ancestors and flags missing or permissive clickjacking protection under Security.

FAQ

Does clickjacking only affect pages with buttons?

No. Any interactive element can be the target — form submissions, file upload triggers, drag-and-drop interactions, and even mouse movements (cursor jacking). The attack is especially dangerous on pages with one-click actions like payment confirmation, account deletion, or OAuth authorization.

Can clickjacking steal passwords or credit card numbers?

Not directly — the attacker cannot read the content of the framed page due to the browser's same-origin policy. What clickjacking achieves is tricking the user into performing an authenticated action they did not intend. For credential theft, attackers use different techniques like phishing.

Is X-Frame-Options a deprecated header?

Not deprecated, but superseded. X-Frame-Options still works in all browsers and should still be sent as a fallback. The W3C recommends using frame-ancestors in CSP as the primary mechanism, with X-Frame-Options as a backwards-compatible companion.

Does frame-ancestors in CSP need to be a separate header from the rest of my CSP?

No — it should be part of the same Content-Security-Policy header as your other directives, separated by a semicolon. Sending two separate Content-Security-Policy headers is valid but the browser applies the most restrictive combination, which can cause unexpected behaviour. Keep it in one header.

Can I use frame-ancestors to protect a widget that third parties embed?

Yes, but carefully. If your widget is meant to be embedded anywhere, you cannot enumerate all allowed origins. In that case, set frame-ancestors * only on the specific widget endpoint, and keep frame-ancestors 'none' everywhere else. Wildcard framing should be the exception, not the default.

Does clickjacking affect mobile browsers?

Yes. Modern mobile browsers based on Chromium, WebKit, and Gecko inherit desktop behaviour for both X-Frame-Options and frame-ancestors. Touch events can also be hijacked — a tap on a mobile page embedded in an invisible iframe registers as an interaction on the underlying page just as a mouse click does on desktop.

Why does OWASP list three defences instead of one?

The OWASP Clickjacking Defense Cheat Sheet recommends defence in depth because each layer covers cases the others miss. frame-ancestors is the modern enforcement; X-Frame-Options is the fallback for older or non-compliant browsers; and SameSite cookies neutralise authenticated cross-origin frame interactions even if the framing headers are bypassed. Combining all three is the recommended baseline.

Conclusion

Preventing clickjacking is straightforward: send X-Frame-Options: DENY and Content-Security-Policy: frame-ancestors 'none' on every response. Do it globally in your server configuration so no page is accidentally left unprotected. If specific routes need to be embeddable, override only those routes with a permissive frame-ancestors pointing to trusted origins. Run a Greadme deep scan to check whether your framing headers are present and correctly configured across your site.