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.
Content-Security-Policy: frame-ancestors 'none' (modern) plus X-Frame-Options: DENY (legacy fallback).frame-ancestors, X-Frame-Options, and SameSite cookies.X-Frame-Options is documented in RFC 7034 (Informational, October 2013); frame-ancestors is part of W3C CSP Level 3.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.
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:
| Value | Meaning | Use when |
|---|---|---|
DENY | The page cannot be embedded in any frame, regardless of origin | Default choice — use on all pages that do not need to be framed |
SAMEORIGIN | The page can only be embedded in a frame on the same origin | When your own site embeds one of its own pages in an iframe |
ALLOW-FROM uri | The page can only be embedded by the specified origin | Obsolete — 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: SAMEORIGINThe 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.
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.
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=StrictImportant 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.
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.
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.
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.
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.
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.
X-Frame-Options and/or Content-Security-Policy with frame-ancestors appear on the responses for your sensitive pages.<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.X-Frame-Options and frame-ancestors and flags missing or permissive clickjacking protection under Security.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.
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.
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.
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.
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.
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.
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.
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.