Skip to main content

Clickjacking Prevention Guide: frame-ancestors, X-Frame-Options, and How to Test

By Joseph Abear ·
Clickjacking explained title graphic with a shield icon representing web attack prevention.

Clickjacking is prevented by sending a Content-Security-Policy header with the frame-ancestors directive (set to 'none' or 'self'), keeping X-Frame-Options as a legacy fallback, and setting authentication cookies to SameSite=Lax or Strict. The attack works by loading your real page inside an invisible iframe on a page the attacker controls, so a victim who thinks they are clicking a game button is actually clicking "confirm," "delete," or "authorize" on your site while authenticated. The fix is response headers, and you can verify them in under a minute with curl.

TL;DR: Quick answer

  • The OWASP Clickjacking Defense Cheat Sheet recommends CSP frame-ancestors as the primary control; it is part of CSP Level 2 and lets you authorize zero, one, or multiple framing origins.
  • X-Frame-Options (DENY or SAMEORIGIN) is obsoleted by frame-ancestors but still worth sending for older browser coverage; when both are present, modern browsers honor frame-ancestors and ignore X-Frame-Options.
  • SameSite=Lax or Strict on session cookies blunts framed attacks because the victim's session cookie is not sent with cross-site iframe requests.
  • Test with one command: curl -sI https://example.com and read the headers, or run the site through securityheaders.com for a graded report.
  • For healthcare sites, a frameable patient portal is an integrity and access-control problem under 45 CFR § 164.312(c) and § 164.312(a), not just a hardening miss.

How does a clickjacking attack actually work?

The attacker builds a page they control, typically a fake prize page, survey, or video player. Into that page they embed your site in an iframe styled with full opacity transparency (opacity: 0) and positioned absolutely so that one of your buttons sits exactly under their visible decoy button. The victim is logged in to your site already, so the iframe renders their authenticated session.

The victim clicks "Play." The click passes through the invisible layer to your framed page and lands on whatever sits beneath: a funds transfer confirmation, an email-change submit, an OAuth consent grant, a "share my records" toggle. The browser treats it as a legitimate, user-initiated click inside an authenticated session, because mechanically that is what it is. Variants include likejacking (hijacking social media buttons), cursorjacking (offsetting the displayed cursor from the real one), and drag-and-drop attacks that pull data out of the framed page.

The defining property: nothing on your server is exploited. No injection, no broken auth. The vulnerability is the browser's willingness to render your page inside someone else's frame, which is why the defense is telling browsers not to.

CSP frame-ancestors: the primary defense

The frame-ancestors directive of the Content-Security-Policy header controls which origins may embed your page in a frame, iframe, object, or embed element. Three configurations cover nearly every case:

  • Content-Security-Policy: frame-ancestors 'none' blocks all framing. This is the right default for login pages, portals, and admin interfaces.
  • Content-Security-Policy: frame-ancestors 'self' allows framing only by pages from the same origin, for sites that legitimately frame their own content.
  • Content-Security-Policy: frame-ancestors 'self' https://partner.example.com grants an explicit allowlist, something X-Frame-Options never supported cleanly.

Two operational details matter. First, frame-ancestors must be delivered as an HTTP response header; it is ignored in a meta tag. Second, you can stage the rollout with Content-Security-Policy-Report-Only plus a report endpoint, watch for legitimate framing you forgot about (embedded widgets, iframe-based page builders, payment flows), then enforce. That report-only path is the main reason OWASP favors CSP here: you can deploy to production without guessing.

X-Frame-Options: keep it as a fallback

X-Frame-Options predates CSP and supports two reliable values: DENY (no framing at all) and SAMEORIGIN (same-origin framing only). The ALLOW-FROM value was never supported consistently and should not be used. The header is formally obsolete in favor of frame-ancestors, but it costs nothing to send and covers legacy browsers, so the standard pattern is to send both:

  • Content-Security-Policy: frame-ancestors 'none'
  • X-Frame-Options: DENY

Keep the two consistent. When both headers are present, browsers that support CSP Level 2 use frame-ancestors and disregard X-Frame-Options, so a mismatch (say, SAMEORIGIN in one and 'none' in the other) produces different behavior in different browsers and confuses every future audit.

SameSite cookies and why framebusting scripts are not enough

SameSite is defense in depth rather than a framing control. With SameSite=Lax (the default behavior in Chromium-based browsers since 2020) or Strict, the session cookie is not attached to requests made from a cross-site iframe, so even if a page gets framed, the embedded view is unauthenticated and there is nothing useful to click. Set it explicitly on session cookies rather than relying on browser defaults, and reserve SameSite=None for the rare third-party-context cookie that genuinely needs it.

JavaScript framebusting (checking whether window.self equals window.top and breaking out) was the pre-header defense, and it loses. The iframe sandbox attribute, among other techniques, lets an attacker neutralize the script. Treat framebusting as a last resort for environments where you genuinely cannot set response headers, and treat those environments as a problem to fix.

How do you test your anti-clickjacking headers?

  • curl: run curl -sI https://yoursite.com/login and look for the Content-Security-Policy and X-Frame-Options lines. Check the pages that matter (login, portal, settings), not just the homepage; headers set in one server block or application route often miss others.
  • securityheaders.com: a free scanner that grades your response headers and flags missing anti-framing directives, useful as a shareable before/after artifact.
  • Browser proof: create a local HTML file containing an iframe pointed at your page. With correct headers the frame stays blank and the console logs a refusal. This is the test that convinces stakeholders, because they watch the attack fail.
  • Scanners: OWASP ZAP and Burp Suite both flag missing anti-framing headers during routine scans, which keeps the check from regressing after the next infrastructure change.

Where you set the header depends on the stack: an Nginx add_header directive or Apache Header set line, a CloudFront response headers policy or load balancer rule on AWS, or middleware in the application itself. Setting it at the edge covers every route at once, which is usually the safer choice.

Why clickjacking matters more on healthcare sites

A frameable page is dangerous in proportion to what a logged-in user can do on it, and patient portals concentrate exactly the high-value actions clickjacking abuses: viewing records containing PHI, changing contact email (a account-takeover pivot), authorizing record sharing, confirming or canceling appointments. Health credentials and records also resell at a premium, which keeps healthcare login pages on target lists.

For a Covered Entity or Business Associate, there is a regulatory mapping too. A UI-redress attack that tricks an authenticated patient into altering or disclosing ePHI implicates the integrity standard at 45 CFR § 164.312(c) (protecting ePHI from improper alteration) and the access control standard at § 164.312(a). Anti-framing headers are a cheap, documentable technical safeguard to record in the risk analysis required by § 164.308(a)(1)(ii)(A). Server-side controls and client-side hardening cover different surfaces; our overview of HIPAA hosting security measures covers the server side, and a missing header is exactly the kind of quiet gap we catalog in unintentional HIPAA violation examples. Developers who ship healthcare frontends should fold header checks into the baseline covered in our HIPAA training guide for web developers.

Frequently asked questions

What single header should I set to stop clickjacking?

Content-Security-Policy: frame-ancestors 'none' (or 'self' if you frame your own pages), delivered as an HTTP response header. Add X-Frame-Options: DENY as a legacy fallback.

Does frame-ancestors override X-Frame-Options?

Yes. In browsers that support CSP Level 2, which is effectively all current browsers, frame-ancestors takes precedence and X-Frame-Options is ignored when both are present.

Is clickjacking still a real threat in 2026?

Yes. It remains a standard finding in penetration tests because the headers are easy to omit, and any frameable page with authenticated, clickable actions is exploitable. Newer variants like double-clickjacking, which abuses the timing of double-clicks, keep the technique current.

Will these headers break my embedded widgets or page builder?

They can, which is why CSP supports Report-Only mode. Run frame-ancestors in report-only, collect violations for a week, allowlist the legitimate origins, then enforce.

Is a missing X-Frame-Options header a HIPAA violation?

Not per se; the Security Rule names standards, not headers. But a frameable portal weakens the integrity and access-control safeguards at § 164.312(c) and § 164.312(a), and an unmitigated, undocumented weakness is what HHS OCR looks for in a post-incident review.

Where to go from here

Anti-framing headers are one layer of a hardened healthcare deployment; the full stack view is in our complete guide to HIPAA-compliant hosting. hipaacomplianthosting.com provides managed HIPAA hosting with security headers configured at the edge as part of the build; that is our business, and you can ask us to check your headers if you want a second pair of eyes.

This article is general information, not legal advice. Consult qualified counsel and base your safeguards on a documented risk analysis under 45 CFR § 164.308(a)(1)(ii)(A). Reviewed June 2026.

Sources