Master the Content Security Policy frame-ancestors directive for modern clickjacking protection
The Content Security Policy (CSP) frame-ancestors directive is the modern, flexible replacement for the X-Frame-Options header. It provides granular control over which websites are allowed to embed your content in iframes, frames, or objects.
Unlike X-Frame-Options, CSP frame-ancestors offers more sophisticated control mechanisms, including the ability to specify multiple allowed domains, use wildcards, and combine with other CSP directives for comprehensive security policies.
CSP frame-ancestors provides the flexibility needed for modern web applications while maintaining strong security. It's the recommended approach for new applications and offers future-proof protection against clickjacking attacks.
Prevents your site from being displayed in any iframe, regardless of the originating site. This is equivalent to X-Frame-Options: DENY and provides the most restrictive protection.
Content-Security-Policy: frame-ancestors 'none';Allows your site to be framed only by pages from the same origin (same domain, protocol, and port). This is equivalent to X-Frame-Options: SAMEORIGIN.
Content-Security-Policy: frame-ancestors 'self';Allows your site to be framed only by specific, trusted domains. This provides granular control that X-Frame-Options cannot match.
Content-Security-Policy: frame-ancestors 'self' https://trusted.com;Content-Security-Policy: frame-ancestors https://partner1.com https://partner2.com;Use wildcards to allow framing by multiple domains matching specific patterns.
Content-Security-Policy: frame-ancestors *.example.com;Content-Security-Policy: frame-ancestors https://*.partner.com;*.example.com - All subdomains of example.comhttps://*.partner.com - HTTPS subdomains of partner.com*://*.trusted.com - Any protocol for trusted.com subdomainsConfigure CSP frame-ancestors in Apache:
# In httpd.conf or .htaccess file
# Basic 'none' configuration
Header always set Content-Security-Policy "frame-ancestors 'none';"
# 'self' configuration
Header always set Content-Security-Policy "frame-ancestors 'self';"
# Specific domains
Header always set Content-Security-Policy "frame-ancestors 'self' https://trusted.com;"
# Multiple domains with wildcards
Header always set Content-Security-Policy "frame-ancestors 'self' https://*.partner.com;"
# Combined with other CSP directives
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; frame-ancestors 'none';"
# Environment-based configuration
<If "%{ENV:ENVIRONMENT}" == "production">
Header always set Content-Security-Policy "frame-ancestors 'none';"
</If>
<Else>
Header always set Content-Security-Policy "frame-ancestors 'self';"
</Else>Set CSP frame-ancestors in Nginx:
# In nginx.conf or site configuration
# Basic 'none' configuration
add_header Content-Security-Policy "frame-ancestors 'none';" always;
# 'self' configuration
add_header Content-Security-Policy "frame-ancestors 'self';" always;
# Specific domains
add_header Content-Security-Policy "frame-ancestors 'self' https://trusted.com;" always;
# Multiple domains
add_header Content-Security-Policy "frame-ancestors 'self' https://partner1.com https://partner2.com;" always;
# Wildcard domains
add_header Content-Security-Policy "frame-ancestors 'self' https://*.partner.com;" always;
# Full CSP policy
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; frame-ancestors 'none';" always;
# Location-specific configuration
location /admin/ {
add_header Content-Security-Policy "frame-ancestors 'none';" always;
}
location /embed/ {
add_header Content-Security-Policy "frame-ancestors 'self' https://partner.com;" always;
}Use helmet middleware for Express.js:
const express = require('express');
const helmet = require('helmet');
const app = express();
// Basic CSP configuration
app.use(helmet({
contentSecurityPolicy: {
directives: {
frameAncestors: ["'none'"],
},
},
}));
// 'self' configuration
app.use(helmet({
contentSecurityPolicy: {
directives: {
frameAncestors: ["'self'"],
},
},
}));
// Specific domains
app.use(helmet({
contentSecurityPolicy: {
directives: {
frameAncestors: ["'self'", "https://trusted.com"],
},
},
}));
// Wildcard domains
app.use(helmet({
contentSecurityPolicy: {
directives: {
frameAncestors: ["'self'", "https://*.partner.com"],
},
},
}));
// Full CSP policy
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
frameAncestors: ["'none'"],
},
},
}));
// Conditional configuration
app.use((req, res, next) => {
const csp = helmet.contentSecurityPolicy({
directives: {
frameAncestors: req.path.startsWith('/admin') ? ["'none'"] : ["'self'"],
},
});
csp(req, res, next);
});
// Manual header setting
app.use((req, res, next) => {
res.setHeader('Content-Security-Policy', "frame-ancestors 'none';");
next();
});Set CSP headers in PHP applications:
<?php
// Basic CSP header
header("Content-Security-Policy: frame-ancestors 'none';");
// 'self' configuration
header("Content-Security-Policy: frame-ancestors 'self';");
// Specific domains
header("Content-Security-Policy: frame-ancestors 'self' https://trusted.com;");
// Multiple domains
header("Content-Security-Policy: frame-ancestors 'self' https://partner1.com https://partner2.com;");
// Wildcard domains
header("Content-Security-Policy: frame-ancestors 'self' https://*.partner.com;");
// Full CSP policy
header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'none';");
// Conditional based on page
if ($isAdminPage) {
header("Content-Security-Policy: frame-ancestors 'none';");
} else {
header("Content-Security-Policy: frame-ancestors 'self';");
}
// WordPress functions.php
add_action('send_headers', function() {
header('Content-Security-Policy: frame-ancestors 'none';');
});
// Laravel middleware
class CSPFrameAncestorsMiddleware
{
public function handle($request, Closure $next, $policy = "'none'")
{
$response = $next($request);
$response->header('Content-Security-Policy', "frame-ancestors $policy;");
return $response;
}
}
// Symfony
public function onKernelResponse(ResponseEvent $event)
{
$response = $event->getResponse();
$response->headers->set('Content-Security-Policy', "frame-ancestors 'none';");
}
?>For static sites or when server configuration isn't available:
<!DOCTYPE html>
<html>
<head>
<!-- Basic CSP meta tag -->
<meta http-equiv="Content-Security-Policy"
content="frame-ancestors 'none';">
<!-- 'self' configuration -->
<meta http-equiv="Content-Security-Policy"
content="frame-ancestors 'self';">
<!-- Specific domains -->
<meta http-equiv="Content-Security-Policy"
content="frame-ancestors 'self' https://trusted.com;">
<!-- Full CSP policy -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self'; frame-ancestors 'none';">
<!-- Note: HTTP headers are preferred over meta tags -->
</head>
<body>
<!-- Your content here -->
</body>
</html>HTTP headers are preferred over meta tags for CSP. Meta tags should only be used when server configuration isn't possible or as a fallback mechanism.
CSP frame-ancestors works best as part of a comprehensive security policy:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.trusted.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://images.example.com; font-src 'self' https://fonts.googleapis.com; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests;
Generate CSP policies dynamically based on user context or application state:
// Dynamic CSP generation based on user role
function generateCSP(userRole, allowedPartners = []) {
let frameAncestors = "'none'";
if (userRole === 'admin') {
frameAncestors = "'none'";
} else if (userRole === 'partner') {
frameAncestors = "'self' " + allowedPartners.join(' ');
} else {
frameAncestors = "'self'";
}
return {
"default-src": ["'self'"],
"script-src": ["'self'"],
"style-src": ["'self'", "'unsafe-inline'"],
"frame-ancestors": [frameAncestors]
};
}
// Usage in Express middleware
app.use((req, res, next) => {
const user = req.user;
const csp = generateCSP(user?.role, user?.allowedPartners || []);
const cspString = Object.entries(csp)
.map(([key, values]) => `${key.replace(/([A-Z])/g, '-$1').toLowerCase()} ${values.join(' ')}`)
.join('; ');
res.setHeader('Content-Security-Policy', cspString);
next();
});Monitor CSP violations to detect potential issues or attacks:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
frame-ancestors 'none';
report-uri /csp-violation-report;
report-to csp-endpoint;
Report-To: {
"group": "csp-endpoint",
"max_age": 10886400,
"endpoints": [{"url": "https://reports.example.com/csp"}]
}
// CSP Violation Report Handler (Node.js)
app.post('/csp-violation-report', express.json(), (req, res) => {
const violation = req.body;
console.log('CSP Violation:', {
blockedURI: violation['blocked-uri'],
documentURI: violation['document-uri'],
violatedDirective: violation['violated-directive'],
originalPolicy: violation['original-policy'],
timestamp: new Date().toISOString()
});
// Store violation for analysis
storeCSPViolation(violation);
res.status(204).end();
});X-Frame-Options: DENY→ frame-ancestors 'none'
X-Frame-Options: SAMEORIGIN→ frame-ancestors 'self'
X-Frame-Options: ALLOW-FROM uri→ frame-ancestors uri
Migrate gradually to ensure compatibility and test thoroughly:
For maximum compatibility, implement both headers during transition:
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none';document.location.protocolGoogle's CSP evaluator tool for testing policies
Online tool for testing CSP directives
Security testing framework with CSP support
Check console for CSP violation reports
Create comprehensive tests for your CSP implementation:
// Jest/Puppeteer test example
describe('CSP frame-ancestors', () => {
test('should prevent iframe embedding', async () => {
const response = await page.goto('https://your-site.com');
const cspHeader = response.headers()['content-security-policy'];
expect(cspHeader).toContain("frame-ancestors 'none'");
});
test('should block cross-origin iframe', async () => {
await page.goto('https://attacker-site.com');
// Try to embed your site
await page.evaluate(() => {
const iframe = document.createElement('iframe');
iframe.src = 'https://your-site.com';
document.body.appendChild(iframe);
});
// Check if iframe is blocked
const iframeBlocked = await page.evaluate(() => {
const iframe = document.querySelector('iframe');
return iframe.src === 'about:blank' || !iframe.contentWindow;
});
expect(iframeBlocked).toBe(true);
});
});CSP frame-ancestors is set but site still loads in iframe.
'none' policy breaks legitimate iframe usage.
Receiving CSP violation reports for frame-ancestors.
Verify your CSP frame-ancestors policy is properly configured and protecting against clickjacking
Maximum 60 characters