X-Frame-Options Complete Guide

Master the X-Frame-Options header for effective clickjacking protection

Understanding X-Frame-Options

The X-Frame-Options HTTP response header is a security mechanism that helps protect your website from clickjacking attacks. It indicates whether your site should be allowed to be displayed in an iframe, frame, or object on other websites.

Originally developed by Microsoft for Internet Explorer 8, X-Frame-Options has been widely adopted by all major browsers and remains one of the most effective ways to prevent clickjacking attacks. While newer technologies like CSP frame-ancestors exist, X-Frame-Options continues to be essential for comprehensive protection.

🎯 Why X-Frame-Options Matters

Clickjacking attacks rely on embedding legitimate websites in invisible iframes. X-Frame-Options directly prevents this by telling browsers not to display your site in frames, effectively blocking the attack vector at its source.

X-Frame-Options Directive Values

DENY

The most restrictive and secure option. Completely prevents your site from being displayed in any iframe, regardless of the originating site.

Implementation:

X-Frame-Options: DENY

When to Use:

  • • Banking and financial applications
  • • Authentication pages
  • • Admin panels
  • • Any application handling sensitive data
  • • When you never want your site framed

SAMEORIGIN

Allows your site to be framed only by pages from the same origin (same domain, protocol, and port). This provides flexibility while maintaining security.

Implementation:

X-Frame-Options: SAMEORIGIN

When to Use:

  • • Applications with internal framing requirements
  • • Sites with legitimate same-origin iframes
  • • Web applications with embedded components
  • • When internal framing is needed but external framing is not

ALLOW-FROM uri

Allows your site to be framed only by specific origins. Note: This directive is deprecated in many modern browsers and may not be supported.

Implementation:

X-Frame-Options: ALLOW-FROM https://trusted-site.com

⚠️ Deprecation Warning:

Most modern browsers no longer support ALLOW-FROM. Use CSP frame-ancestors instead for granular control over which sites can frame your content.

Implementation Guide

Apache Server Configuration

Add X-Frame-Options headers to your Apache configuration:

# In httpd.conf or .htaccess file

# Basic DENY configuration
Header always set X-Frame-Options "DENY"

# SAMEORIGIN configuration
Header always set X-Frame-Options "SAMEORIGIN"

# ALLOW-FROM configuration (deprecated)
Header always set X-Frame-Options "ALLOW-FROM https://trusted-site.com"

# Multiple headers for different environments
<If "%{HTTP_HOST} == 'admin.example.com'">
    Header always set X-Frame-Options "DENY"
</If>
<Else>
    Header always set X-Frame-Options "SAMEORIGIN"
</Else>

Nginx Server Configuration

Configure X-Frame-Options in your Nginx server block:

# In nginx.conf or site configuration

# Basic DENY configuration
add_header X-Frame-Options "DENY" always;

# SAMEORIGIN configuration
add_header X-Frame-Options "SAMEORIGIN" always;

# ALLOW-FROM configuration (deprecated)
add_header X-Frame-Options "ALLOW-FROM https://trusted-site.com" always;

# Conditional configuration
server {
    listen 443 ssl;
    server_name admin.example.com;
    add_header X-Frame-Options "DENY" always;
    # ... other configuration
}

server {
    listen 443 ssl;
    server_name app.example.com;
    add_header X-Frame-Options "SAMEORIGIN" always;
    # ... other configuration
}

Node.js Express Implementation

Use helmet middleware for Express.js applications:

const express = require('express');
const helmet = require('helmet');
const app = express();

// Basic helmet configuration
app.use(helmet({
  frameguard: { action: 'deny' }        // DENY
  // frameguard: { action: 'sameorigin' } // SAMEORIGIN
}));

// Custom configuration
app.use(helmet.frameguard({
  action: 'deny'
}));

// Conditional configuration
app.use((req, res, next) => {
  if (req.path.startsWith('/admin')) {
    res.setHeader('X-Frame-Options', 'DENY');
  } else {
    res.setHeader('X-Frame-Options', 'SAMEORIGIN');
  }
  next();
});

// Manual header setting
app.use((req, res, next) => {
  res.setHeader('X-Frame-Options', 'DENY');
  next();
});

PHP Implementation

Set headers in PHP applications:

<?php
// Basic header setting
header("X-Frame-Options: DENY");

// SAMEORIGIN
header("X-Frame-Options: SAMEORIGIN");

// ALLOW-FROM (deprecated)
header("X-Frame-Options: ALLOW-FROM https://trusted-site.com");

// Conditional setting based on page type
if ($isAdminPage) {
    header("X-Frame-Options: DENY");
} else {
    header("X-Frame-Options: SAMEORIGIN");
}

// WordPress functions.php
add_action('send_headers', function() {
    header('X-Frame-Options: DENY');
});

// Laravel middleware
class XFrameOptionsMiddleware
{
    public function handle($request, Closure $next, $option = 'DENY')
    {
        $response = $next($request);
        $response->header('X-Frame-Options', $option);
        return $response;
    }
}
?>

ASP.NET Implementation

Configure in web.config or code:

<!-- In web.config -->
<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="X-Frame-Options" value="DENY" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

// In Global.asax or Startup.cs
protected void Application_BeginRequest()
{
    HttpContext.Current.Response.AddHeader("X-Frame-Options", "DENY");
}

// ASP.NET Core middleware
app.Use(async (context, next) =>
{
    context.Response.Headers.Add("X-Frame-Options", "DENY");
    await next();
});

// Using NWebSec library
app.UseXfo(options => options.SameOrigin());

Browser Compatibility

X-Frame-Options is supported by all major browsers, but there are some differences in implementation and behavior:

Browser Support Matrix

BrowserVersionDENYSAMEORIGINALLOW-FROM
Chrome4.0+
Firefox3.6.9+
Safari4.0+
EdgeAll
Internet Explorer8.0+

⚠️ Important Notes

  • • ALLOW-FROM is deprecated in Chrome, Firefox, and Safari
  • • Use CSP frame-ancestors for modern granular control
  • • Internet Explorer 8 was the first browser to support X-Frame-Options
  • • Mobile browsers generally follow desktop browser behavior

Common Issues and Solutions

Issue: Header Not Working

X-Frame-Options header is set but site still loads in iframe.

Solutions:

  • • Check browser developer tools for header presence
  • • Ensure header is set on all responses (including errors)
  • • Verify no conflicting CSP frame-ancestors header
  • • Check for multiple X-Frame-Options headers

Issue: Breaking Legitimate Iframes

DENY setting breaks legitimate iframe usage.

Solutions:

  • • Use SAMEORIGIN instead of DENY
  • • Implement CSP frame-ancestors for specific domains
  • • Use conditional header setting based on page
  • • Consider using postMessage for iframe communication

Issue: Multiple Headers Conflict

Multiple X-Frame-Options headers cause unpredictable behavior.

Solutions:

  • • Ensure only one X-Frame-Options header is sent
  • • Check web server configuration for duplicate headers
  • • Use application-level header setting for consistency
  • • Monitor headers with browser dev tools

Testing and Validation

Browser Developer Tools Method

Step-by-Step Testing:

  1. 1Open your website in Chrome/Firefox
  2. 2Press F12 to open Developer Tools
  3. 3Go to Network tab
  4. 4Refresh the page (Ctrl+R or Cmd+R)
  5. 5Click on the main document request
  6. 6Check Response Headers for X-Frame-Options

Manual iframe Testing

Create a test HTML file to verify iframe blocking:

<!DOCTYPE html>
<html>
<head>
    <title>X-Frame-Options Test</title>
    <style>
        body { font-family: Arial, sans-serif; padding: 20px; }
        iframe { border: 2px solid #333; width: 800px; height: 600px; }
        .test-result { margin-top: 20px; padding: 10px; }
        .success { background: #d4edda; color: #155724; }
        .error { background: #f8d7da; color: #721c24; }
    </style>
</head>
<body>
    <h1>X-Frame-Options Test</h1>
    <p>Testing iframe embedding for: https://your-site.com</p>
    
    <iframe src="https://your-site.com" 
            id="test-iframe"
            onload="showSuccess()"
            onerror="showError()">
    </iframe>
    
    <div id="result" class="test-result">
        Loading test...
    </div>
    
    <script>
        function showSuccess() {
            document.getElementById('result').className = 'test-result error';
            document.getElementById('result').innerHTML = 
                '❌ Site loaded in iframe - X-Frame-Options not working!';
        }
        
        function showError() {
            document.getElementById('result').className = 'test-result success';
            document.getElementById('result').innerHTML = 
                '✅ Site blocked in iframe - X-Frame-Options working!';
        }
        
        // Fallback check after timeout
        setTimeout(() => {
            const iframe = document.getElementById('test-iframe');
            if (iframe.style.display === 'none' || 
                !iframe.contentWindow) {
                showSuccess();
            }
        }, 5000);
    </script>
</body>
</html>

Online Testing Tools

securityheaders.com

Free tool to check HTTP security headers including X-Frame-Options

OWASP ZAP

Comprehensive security testing tool with clickjacking detection

Burp Suite

Professional web application security testing platform

curl Command

Command-line tool to check HTTP headers

Best Practices and Recommendations

🔧 Implementation Best Practices

  • Always use DENY for sensitive applications
  • Set headers at the web server level for consistency
  • Test headers in multiple browsers
  • Monitor headers regularly for changes
  • Use CSP frame-ancestors for modern applications

🛡️ Security Considerations

  • Combine with other security headers
  • Implement defense-in-depth strategy
  • Regular security testing and audits
  • Stay updated on browser changes
  • Document security configurations

Test Your X-Frame-Options Implementation

Verify your X-Frame-Options header is properly configured and working

Maximum 60 characters