ADFS 3.0 enforces strict clickjacking protection by default through its X-Frame-Options: DENY
header. While this is excellent for security, it becomes problematic when legitimate integrations require embedding ADFS pages in iframes.
Unlike modern web applications, ADFS 3 doesn't provide native configuration options for modifying frame options. The server-side components are compiled binaries, making direct modification challenging. Here are three potential approaches:
// Potential solution paths:
1. Reverse proxy modification
2. Custom HTTP module
3. WAF (Web Application Firewall) rules
The most maintainable solution involves using a reverse proxy to rewrite headers. Here's an IIS URL Rewrite example:
<rule name="ADFS X-Frame-Options Override" enabled="true">
<match serverVariable="RESPONSE_X-Frame-Options" pattern="DENY" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{HTTP_REFERER}" pattern="^https://trusted\.domain\.com/" />
</conditions>
<action type="Rewrite" value="ALLOW-FROM https://trusted.domain.com" />
</rule>
For more granular control, you can implement a custom HTTP module in C#:
public class FrameOptionsModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.PreSendRequestHeaders += OnPreSendHeaders;
}
private void OnPreSendHeaders(object sender, EventArgs e)
{
HttpContext context = HttpContext.Current;
if (context.Request.Url.AbsolutePath.StartsWith("/adfs/"))
{
if (IsTrustedDomain(context.Request.UrlReferrer))
{
context.Response.Headers.Remove("X-Frame-Options");
context.Response.AddHeader("X-Frame-Options",
$"ALLOW-FROM {context.Request.UrlReferrer.GetLeftPart(UriPartial.Authority)}");
}
}
}
private bool IsTrustedDomain(Uri referrer)
{
// Implement your domain validation logic here
return referrer?.Host.EndsWith("trusted.com") ?? false;
}
}
Before implementing any solution, consider these security aspects:
- Maintain a strict whitelist of allowed domains
- Implement additional CSRF protections
- Consider using Content Security Policy (CSP) frame-ancestors as a modern alternative
- Regularly audit iframe-embedded pages
Verify your solution with this test HTML:
<!DOCTYPE html>
<html>
<body>
<iframe src="https://adfs.yourdomain.com/adfs/ls/"
width="800" height="600">
</iframe>
<script>
window.addEventListener('message', function(e) {
if (e.data === 'framed') {
console.log('ADFS loaded successfully in frame');
}
});
</script>
</body>
</html>
Remember to test with both whitelisted and non-whitelisted domains to verify your security controls work as intended.
ADFS 3.0 by default enforces strict framing protection through its X-Frame-Options: DENY
header. While this effectively prevents clickjacking attacks, it creates integration challenges when legitimate iframe embedding is required for specific domains.
The current implementation doesn't expose native configuration options for modifying this header. Before proceeding with any modification approach, consider:
- Evaluate whether iframe embedding is absolutely necessary
- Maintain strict domain whitelisting
- Consider Content-Security-Policy (CSP) as a modern alternative
Option 1: IIS URL Rewrite Module
This approach modifies the response header at the web server level:
<configuration>
<system.webServer>
<rewrite>
<outboundRules>
<rule name="Modify X-Frame-Options" preCondition="IsADFSResponse">
<match serverVariable="RESPONSE_X-Frame-Options" pattern="DENY" />
<action type="Rewrite" value="ALLOW-FROM https://trusted.domain.com" />
</rule>
<preConditions>
<preCondition name="IsADFSResponse">
<add input="{URL}" pattern="^/adfs/ls/.*" />
</preCondition>
</preConditions>
</outboundRules>
</rewrite>
</system.webServer>
</configuration>
Option 2: Custom HTTP Module
A more flexible .NET solution that allows dynamic domain validation:
public class FrameOptionsModule : IHttpModule
{
private static readonly HashSet<string> _allowedDomains = new HashSet<string>
{
"trusted.domain.com",
"another-trusted.domain.org"
};
public void Init(HttpApplication context)
{
context.PreSendRequestHeaders += OnPreSendHeaders;
}
private void OnPreSendHeaders(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
if (app.Request.Url.AbsolutePath.StartsWith("/adfs/ls/", StringComparison.OrdinalIgnoreCase))
{
var referrer = app.Request.UrlReferrer;
if (referrer != null && _allowedDomains.Contains(referrer.Host))
{
app.Context.Response.Headers.Set("X-Frame-Options", $"ALLOW-FROM {referrer.Scheme}://{referrer.Host}");
}
}
}
public void Dispose() { }
}
Consider implementing CSP frame-ancestors directive alongside or instead of X-Frame-Options:
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Content-Security-Policy"
value="frame-ancestors https://trusted.domain.com https://another.domain.org" />
</customHeaders>
</httpProtocol>
</system.webServer>
- Thoroughly test all authentication flows after modification
- Maintain an audit log of iframed authentication attempts
- Consider implementing additional anti-CSRF measures
- Document the security exception and review periodically
After implementation, verify using browser developer tools:
- Check the Network tab for X-Frame-Options or CSP headers
- Test with both whitelisted and non-whitelisted domains
- Verify all ADFS endpoints (/adfs/ls, /adfs/oauth2, etc.)
- Confirm no mixed content warnings appear