Refused to display in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'
Categories:
Resolving 'X-Frame-Options: SAMEORIGIN' Errors in Web Applications

Understand and troubleshoot the 'Refused to display in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'' error, commonly encountered when embedding content or integrating with services like Google OAuth.
The 'Refused to display in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'' error is a common security measure implemented by web servers to prevent clickjacking attacks. This error occurs when a web page attempts to load content from another domain within an <iframe>
, <frame>
, or <object>
tag, but the server hosting the content explicitly forbids it using the X-Frame-Options
HTTP header. This article will delve into why this error occurs, its implications, and how to resolve it, particularly in contexts involving AngularJS, ASP.NET Web API, and Google OAuth.
Understanding X-Frame-Options and SAMEORIGIN
The X-Frame-Options
HTTP response header indicates whether a browser should be allowed to render a page in a <frame>
, <iframe>
, <embed>
, or <object>
tag. Websites can use this to avoid clickjacking attacks, by ensuring that their content is not embedded into other sites. The SAMEORIGIN
directive means that the page can only be displayed in a frame on the same origin as the page itself. If a page from example.com
tries to embed content from another.com
that has X-Frame-Options: SAMEORIGIN
, the browser will block it.
sequenceDiagram participant Browser participant WebApp participant ExternalService Browser->>WebApp: Request page with iframe WebApp->>ExternalService: Request content for iframe ExternalService-->>WebApp: HTTP Response with 'X-Frame-Options: SAMEORIGIN' WebApp-->>Browser: Page with iframe (containing blocked content) Browser->>Browser: Detects X-Frame-Options header Browser--xBrowser: Refuses to display iframe content Browser->>Browser: Displays 'Refused to display...' error in console
Sequence diagram illustrating how the 'X-Frame-Options: SAMEORIGIN' error occurs.
Common Scenarios and Solutions
This error frequently arises when integrating third-party services or trying to embed content from different domains. Here are some common scenarios and their respective solutions.
X-Frame-Options
on a server should be done with extreme caution, as it can introduce security vulnerabilities like clickjacking if not properly managed. Always understand the security implications before making changes.Scenario 1: Embedding External Content (e.g., Google OAuth)
When integrating with services like Google OAuth, you might encounter this error if you attempt to load the Google authentication page directly within an <iframe>
. Google, like many secure services, sets X-Frame-Options: SAMEORIGIN
or DENY
to protect its users from clickjacking. This is a security feature, not a bug, and you should not try to bypass it by forcing Google's pages into an iframe.
The correct approach for Google OAuth (and similar services) is to use a redirect-based flow, where the user is temporarily redirected to the service's authentication page, and then redirected back to your application with an authorization code or token. This is typically handled by opening a new browser window or tab, or by redirecting the main window.
// Incorrect approach (will likely be blocked by X-Frame-Options)
// <iframe src="https://accounts.google.com/o/oauth2/auth?..."></iframe>
// Correct approach for initiating Google OAuth (example using window.location)
function initiateGoogleOAuth() {
const authUrl = 'https://accounts.google.com/o/oauth2/auth?' +
'client_id=YOUR_CLIENT_ID&' +
'redirect_uri=YOUR_REDIRECT_URI&' +
'response_type=code&' +
'scope=email%20profile';
window.location.href = authUrl; // Redirect the current window
// Or use window.open(authUrl, '_blank'); for a new tab/window
}
Illustrating the correct (redirect-based) and incorrect (iframe-based) approaches for Google OAuth.
Scenario 2: Embedding Your Own Content from a Subdomain or Different Port
If your application is split across subdomains (e.g., app.example.com
trying to embed api.example.com
) or different ports (e.g., localhost:4200
embedding localhost:8080
), the browser will treat these as different origins. In such cases, if the embedded content's server sets X-Frame-Options: SAMEORIGIN
, it will be blocked.
To resolve this, you have a few options:
1. Option 1: Configure the Server to Allow Framing
If you control the server serving the content that is being embedded, you can modify its X-Frame-Options
header. For ASP.NET Web API, you can add a custom header in web.config
or programmatically.
2. Option 2: Use X-Frame-Options: ALLOW-FROM uri
(Deprecated but useful for specific cases)
This directive allows you to specify a single URI that is permitted to frame the document. However, it's deprecated and not supported by all browsers. A more modern approach is to use Content-Security-Policy
.
3. Option 3: Use Content-Security-Policy
(CSP) frame-ancestors
CSP's frame-ancestors
directive is the modern and more flexible way to control framing. It allows you to specify multiple sources that can embed your content. This is the recommended approach for new development.
ASP.NET Web API (web.config)
<system.webServer>
<httpProtocol>
<customHeaders>
<!-- Remove existing X-Frame-Options if present -->
<remove name="X-Frame-Options" />
<!-- Allow framing from specific origins using CSP -->
<add name="Content-Security-Policy" value="frame-ancestors 'self' https://your-app-domain.com;" />
<!-- Or, if you must use X-Frame-Options and only for same origin -->
<!-- <add name="X-Frame-Options" value="SAMEORIGIN" /> -->
</customHeaders>
</httpProtocol>
</system.webServer>
ASP.NET Web API (C# Code)
public class CustomHeaderFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (actionExecutedContext.Response != null)
{
// Remove existing X-Frame-Options if present
if (actionExecutedContext.Response.Headers.Contains("X-Frame-Options"))
{
actionExecutedContext.Response.Headers.Remove("X-Frame-Options");
}
// Add Content-Security-Policy header
actionExecutedContext.Response.Headers.Add("Content-Security-Policy", "frame-ancestors 'self' https://your-app-domain.com;");
// Or, if you must use X-Frame-Options and only for same origin
// actionExecutedContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
}
base.OnActionExecuted(actionExecutedContext);
}
}
// Register the filter in WebApiConfig.cs
// config.Filters.Add(new CustomHeaderFilter());
AngularJS (Client-side)
// AngularJS itself doesn't directly control server-side headers.
// If you're trying to embed content *into* an AngularJS app, the solution
// lies with the server providing the embedded content.
// If your AngularJS app is serving content that *others* embed,
// you'd configure the web server (e.g., Node.js, Nginx, Apache) hosting
// your AngularJS static files to send the appropriate headers.
// Example for a Node.js server serving AngularJS static files:
// const express = require('express');
// const app = express();
// app.use((req, res, next) => {
// res.setHeader('Content-Security-Policy', "frame-ancestors 'self' https://trusted-domain.com;");
// // Or res.setHeader('X-Frame-Options', 'ALLOW-FROM https://trusted-domain.com'); (deprecated)
// next();
// });
// app.use(express.static('dist')); // Assuming 'dist' is your build output
// app.listen(80, () => console.log('Angular app served on port 80'));
Content-Security-Policy
's frame-ancestors
, 'self'
refers to the origin of the document itself. You can list multiple trusted origins separated by spaces.