Introduction
X-Frame-Options is a security response header used to prevent your pages from being embedded in <iframe>, <frame>, or <object> containers on untrusted sites. Its main purpose is to reduce clickjacking risk.
Clickjacking attacks trick users into clicking invisible or disguised UI elements by layering your page under attacker-controlled content.
What Problem Does X-Frame-Options Solve?
Imagine a legitimate banking page loaded inside an attacker page via iframe. The attacker overlays fake buttons and controls where the user clicks. The user believes they are clicking a harmless UI, but the click lands on real sensitive controls in the framed app.
X-Frame-Options tells the browser whether the response is allowed to be framed.
X-Frame-Options Values
The header supports these practical values:
DENYSAMEORIGIN
Historically, ALLOW-FROM existed in some browsers but is obsolete and not reliable.
DENY
Disallow framing by any origin, including your own.
X-Frame-Options: DENY
Use this for sensitive pages that should never appear in an iframe.
SAMEORIGIN
Allow framing only if parent and framed document share the same origin.
X-Frame-Options: SAMEORIGIN
Use this when you legitimately embed your own pages inside your own app shell.
Browser Behavior Example
If target response includes:
X-Frame-Options: SAMEORIGIN
And an external site tries:
<iframe src="https://your-app.example.com/admin"></iframe>
Most modern browsers will block rendering and show an error similar to:
Refused to display 'https://your-app.example.com/admin' in a frame because it set 'X-Frame-Options' to 'sameorigin'.
X-Frame-Options vs CSP frame-ancestors
X-Frame-Options is still useful, but Content Security Policy provides a more modern mechanism:
Content-Security-Policy: frame-ancestors 'self' https://partner.example.com
Why CSP is better
- Supports multiple allowed ancestors.
- Better policy expressiveness.
- Aligns with broader CSP strategy.
Practical guidance
For modern deployments, set both:
X-Frame-Options: SAMEORIGIN(orDENY)Content-Security-Policy: frame-ancestors ...
This gives backward compatibility plus modern control.
Server Configuration Examples
Nginx
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
For highly sensitive routes, override to DENY.
Apache
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Content-Security-Policy "frame-ancestors 'self'"
Express.js (Node)
Use Helmet:
import helmet from 'helmet';
app.use(helmet({
frameguard: { action: 'sameorigin' },
contentSecurityPolicy: {
directives: {
frameAncestors: ["'self'"],
},
},
}));
Django
X_FRAME_OPTIONS = "SAMEORIGIN"
SECURE_CROSS_ORIGIN_OPENER_POLICY = "same-origin"
If you use CSP packages, configure frame-ancestors there as well.
Spring Boot
http
.headers(headers -> headers
.frameOptions(frame -> frame.sameOrigin())
.contentSecurityPolicy(csp -> csp
.policyDirectives("frame-ancestors 'self'"))
);
Common Deployment Mistakes
- Setting header only on some routes but not all HTML responses.
- Forgetting
alwaysin Nginx so errors and redirects miss headers. - Relying on obsolete
ALLOW-FROM. - Assuming API-only JSON endpoints need it (usually unnecessary).
- Using iframe-heavy integrations without planning exceptions.
Route-Specific Policy Strategy
In real systems, not all pages share the same framing requirements.
Example strategy:
- Login, settings, billing:
DENY. - Internal dashboards embedded in same domain shell:
SAMEORIGIN. - Partner-embed pages: CSP
frame-ancestorswith explicit allowlist.
Avoid wildcard or broad partner lists unless strictly required.
Testing Checklist
Quick header check
curl -I https://your-app.example.com
Verify response includes expected security headers.
Browser validation
- Create a test page on another origin with iframe pointing to your route.
- Open devtools console.
- Confirm frame is blocked where expected.
CI automation idea
Add integration tests that assert required headers for security-sensitive routes.
Relationship to Other Headers
X-Frame-Options solves one narrow threat. Pair it with:
Content-Security-PolicyX-Content-Type-Options: nosniffReferrer-PolicyStrict-Transport-SecurityPermissions-Policy
Security headers work best as a bundle, not as isolated toggles.
Conclusion
X-Frame-Options remains a useful baseline defense against clickjacking, but it is no longer the full solution. In 2026, production setups should combine it with CSP frame-ancestors for precise framing control.
If you manage sensitive pages, start with DENY by default and relax only where business requirements demand embedding.
Resources
- MDN: X-Frame-Options
- MDN: CSP frame-ancestors
- OWASP Clickjacking Defense Cheat Sheet
- Helmet security headers
Comments