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.
X-Frame-Options headerHTTP
# 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.

CSP frame-ancestors directiveHTTP
# 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

nginx.confNginx
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

httpd.conf / .htaccessApache
# 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

app.jsJavaScript
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

settings.pyPython
# 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

main.pyPython
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-Options and Content-Security-Policy.
  • curl: curl -I https://yoursite.com and 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.
Test with curlshell
# 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 β†’