Use the CSP frame-ancestors directive to control which sites may embed your pages, and also send X-Frame-Options for older browsers. If both are present, browsers that understand frame-ancestors ignore X-Frame-Options. Neither header works from a meta tag, so they must be set as HTTP response headers.
What the problem is
In a clickjacking attack, a hostile page embeds your site in an invisible iframe and places a decoy button over a real control. The user thinks they click the decoy. They actually click, say, a confirm button inside your page, with their own logged in session. MDN describes the main defense as blocking or limiting framing.
What each header offers
X-Frame-Options is the older, coarse tool. It can forbid framing entirely or allow only same-origin framing. It cannot name a specific partner site that may embed you. The ALLOW-FROM form is obsolete.
frame-ancestors is part of CSP. It takes 'none', 'self', or a list of hosts and schemes. It also checks every ancestor in a nested chain, so a leaf frame must allow all of its parents.
Two details trip people up. The directive does not fall back to default-src, so a policy with default-src 'none' still lets anyone embed you. And the quotes around 'none' and 'self' are mandatory.
What to send
For a page that should never be framed:
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY
For a page that only your own site frames:
Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN
For a page a named partner must embed, use frame-ancestors 'self' https://partner.example.com and drop X-Frame-Options, since it cannot express that. Be aware that older browsers without frame-ancestors support will then have no framing protection on that page.
Where cookies come in
Setting SameSite=Lax or Strict on session cookies means a request from a cross-site frame will not carry the cookie, so the server does not see a logged in user. MDN calls this a partial mitigation. It helps, but it is not a substitute for framing controls. See our post on SameSite Lax, Strict or None for choosing values.
Test it
Create a page on a different site that embeds your sensitive pages. Confirm the browser refuses to render them, and check your pages one by one, since framing headers are often set at the app level but missed on static or error pages. A dynamic scan of your running app can catch pages that lack the header.
Click through it at your own pace, or open it full screen.
Quick check
Can I set frame-ancestors in a meta tag?
No. MDN states the directive is not supported in the meta element. Send it as an HTTP header.
Do I still need X-Frame-Options?
It protects older browsers that lack frame-ancestors. Sending both is the usual advice, and modern browsers use frame-ancestors when both exist.
Sources
Last reviewed: 19 October 2026.