What is clickjacking?
Clickjacking (also called UI redress attack) occurs when an attacker embeds your website in an invisible <iframe> on a malicious page, then positions their own visible UI on top. The victim believes they are clicking a button on the attacker's page, but they are actually clicking a button on your site β which is invisible but positioned precisely beneath the decoy.
Classic attack scenarios:
- Tricking a user into clicking "Delete Account", "Transfer Funds", or "Grant Permission" on a banking or social media site
- Silently enabling camera or microphone permissions on a WebRTC app
- Liking, sharing, or following content on social platforms without user intent
- Bypassing CSRF protections when the attacker can observe the target page layout
OWASP classification: Clickjacking is covered under A05:2021 β Security Misconfiguration in OWASP Top 10 and has a dedicated entry in the OWASP Testing Guide (OTG-CLIENT-009). It is trivial to exploit but equally trivial to prevent.
X-Frame-Options
X-Frame-Options is the original anti-clickjacking header, introduced by Microsoft IE8 in 2009 and subsequently adopted universally. It controls whether the browser is allowed to render your page inside a frame or iframe.
Valid values
DENYβ The page cannot be displayed in any frame, regardless of origin. Use this for pages that should never be embedded (login pages, admin panels, payment flows).SAMEORIGINβ The page can only be embedded by pages from the same origin. Use this if you need to embed your own pages within iframes (e.g., a dashboard widget).ALLOW-FROM uriβ Deprecated. Allowed embedding from a specific origin. Not supported by modern browsers and should not be used.
# Recommended for most applications X-Frame-Options: DENY # If you need same-origin embedding X-Frame-Options: SAMEORIGIN # Do not use β deprecated and unsupported X-Frame-Options: ALLOW-FROM https://trusted.example.com
Content-Security-Policy: frame-ancestors
The frame-ancestors directive in Content Security Policy is the modern replacement for X-Frame-Options. It is more flexible (supports multiple allowed origins) and is the preferred approach for new applications.
# Block all framing Content-Security-Policy: frame-ancestors 'none' # Allow only same origin Content-Security-Policy: frame-ancestors 'self' # Allow specific trusted origins Content-Security-Policy: frame-ancestors 'self' https://dashboard.example.com https://partner.com # Combined with other CSP directives Content-Security-Policy: default-src 'self'; frame-ancestors 'none'; script-src 'self'
frame-ancestors vs X-Frame-Options: frame-ancestors 'none' is equivalent to X-Frame-Options: DENY. frame-ancestors 'self' is equivalent to SAMEORIGIN. However, CSP frame-ancestors supports multiple allowed origins, which X-Frame-Options does not.
Which header should you use?
Set both. X-Frame-Options provides coverage for older browsers that may not support CSP. frame-ancestors is the forward-looking standard and provides more control. When both are present, frame-ancestors takes precedence in browsers that support CSP Level 2.
Recommended configuration: Set X-Frame-Options: DENY and Content-Security-Policy: frame-ancestors 'none' simultaneously for maximum coverage across all browsers.
Nginx implementation
server { # Add to your server block or location block add_header X-Frame-Options "DENY" always; add_header Content-Security-Policy "frame-ancestors 'none'" always; # The 'always' keyword ensures the header is added # even on error responses (4xx, 5xx) }
Nginx inheritance: Headers set in an http block are not automatically inherited by server or location blocks if those blocks also use add_header. Define security headers in every context that needs them, or use include directives.
Apache implementation
# Requires mod_headers to be enabled # Enable: a2enmod headers Header always set X-Frame-Options "DENY" Header always set Content-Security-Policy "frame-ancestors 'none'"
Express / Node.js implementation
const helmet = require('helmet'); // helmet.frameguard sets X-Frame-Options app.use(helmet.frameguard({ action: 'deny' })); // helmet.contentSecurityPolicy sets CSP including frame-ancestors app.use(helmet.contentSecurityPolicy({ directives: { frameAncestors: ["'none'"], // ... other directives } })); // Or set manually without helmet app.use((req, res, next) => { res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('Content-Security-Policy', "frame-ancestors 'none'"); next(); });
Django implementation
# Django has built-in clickjacking protection MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', # ... other middleware ] # Set the X-Frame-Options value # Options: 'DENY' (default) or 'SAMEORIGIN' X_FRAME_OPTIONS = 'DENY' # For CSP frame-ancestors, use django-csp # pip install django-csp CSP_FRAME_ANCESTORS = ("'none'",)
FastAPI / Python implementation
from fastapi import FastAPI, Request, Response from starlette.middleware.base import BaseHTTPMiddleware class SecurityHeadersMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): response = await call_next(request) response.headers["X-Frame-Options"] = "DENY" response.headers["Content-Security-Policy"] = "frame-ancestors 'none'" return response app = FastAPI() app.add_middleware(SecurityHeadersMiddleware)
Testing your implementation
- Browser DevTools: Open DevTools β Network tab β select any response β check Headers for
X-Frame-OptionsandContent-Security-Policy. - curl:
curl -I https://yoursite.comand inspect the response headers. - Security header scanners: securityheaders.com provides a free scan and grades your headers.
- AquilaX DAST: Automatically checks for missing anti-clickjacking headers as part of every scan, with remediation guidance.
# Check security headers on your site curl -I https://yoursite.com | grep -i "x-frame\|content-security" # Expected output x-frame-options: DENY content-security-policy: frame-ancestors 'none'
Scan your application for missing security headers
AquilaX DAST checks for clickjacking protection, HSTS, CSP, and all major security headers β automatically, on every scan.
Run a security header scan β