What is clickjacking?
In a clickjacking attack, an attacker embeds your website inside an invisible iframe on their own page and overlays bait buttons. The user thinks they are clicking "Join for free" — but in reality the click goes through the invisible frame onto, for example, "Delete account" or "Transfer money" on your website where they are currently logged in. Classic victims: banking portals, admin panels, social media sites with like/follow buttons.
How X-Frame-Options works
The HTTP header tells the browser whether your page may be loaded inside an
<iframe>,
<frame> or
<object>.
Three values are possible:
DENY— the page may not be embedded at all, not even by your own domain. Strictest option.SAMEORIGIN— embedding only by pages from the same domain. Useful for internal admin iframes.ALLOW-FROM uri— allows one specific domain. Deprecated, no longer supported by Chrome and Safari.
Configuration examples
nginx:
add_header X-Frame-Options "DENY" always;
Apache:
Header always set X-Frame-Options "SAMEORIGIN"
Express (Node.js):
app.use(helmet.frameguard({ action: 'deny' }))
Flask (Python):
@app.after_request
def set_frame_options(resp):
resp.headers['X-Frame-Options'] = 'DENY'
return resp
frame-ancestors — the modern successor
The Content Security Policy has its own directive for the same problem:
frame-ancestors. It is more
powerful and supported by all modern browsers. Advantages over X-Frame-Options:
- Multiple allowed sources possible — not just one.
- Wildcards and subdomain matching:
*.partner.com. - Clean integration into your overall CSP strategy.
Example:
Content-Security-Policy: frame-ancestors 'none'; Content-Security-Policy: frame-ancestors 'self' https://partner.com;
Both headers at the same time?
Recommendation: set both. X-Frame-Options for older browsers that don't understand CSP frame-ancestors; CSP frame-ancestors for modern browsers. If both are set, CSP wins. That way you're covered on both sides.
What if you need legitimate embeds?
If third-party sites really should embed your website — for example embedded booking
widgets, login buttons or comment widgets — don't enable X-Frame-Options at all and use
frame-ancestors with a precise
whitelist of allowed domains. Never simply use
*.