X-Frame-Options Allow-From multiple domains X-Frame-Options Allow-From multiple domains asp.net asp.net

X-Frame-Options Allow-From multiple domains


X-Frame-Options is deprecated. From MDN:

This feature has been removed from the Web standards. Though some browsers may still support it, it is in the process of being dropped. Do not use it in old or new projects. Pages or Web apps using it may break at any time.

The modern alternative is the Content-Security-Policy header, which along many other policies can white-list what URLs are allowed to host your page in a frame, using the frame-ancestors directive.
frame-ancestors supports multiple domains and even wildcards, for example:

Content-Security-Policy: frame-ancestors 'self' example.com *.example.net ;

Unfortunately, for now, Internet Explorer does not fully support Content-Security-Policy.

UPDATE: MDN has removed their deprecation comment. Here's a similar comment from W3C's Content Security Policy Level

The frame-ancestors directive obsoletes the X-Frame-Options header. If a resource has both policies, the frame-ancestors policy SHOULD be enforced and the X-Frame-Options policy SHOULD be ignored.


From RFC 7034:

Wildcards or lists to declare multiple domains in one ALLOW-FROM statement are not permitted

So,

How do I set the X-Frame-Options: ALLOW-FROM to support more than a single domain?

You can't. As a workaround you can use different URLs for different partners. For each URL you can use it's own X-Frame-Options value. For example:

partner   iframe URL       ALLOW-FROM---------------------------------------Facebook  fb.yoursite.com  facebook.comVK.COM    vk.yoursite.com  vk.com

For yousite.com you can just use X-Frame-Options: deny.

BTW, for now Chrome (and all webkit-based browsers) does not support ALLOW-FROM statements at all.


Necromancing.
The provided answers are incomplete.

First, as already said, you cannot add multiple allow-from hosts, that's not supported.
Second, you need to dynamically extract that value from the HTTP referrer, which means that you can't add the value to Web.config, because it's not always the same value.

It will be necessary to do browser-detection to avoid adding allow-from when the browser is Chrome (it produces an error on the debug - console, which can quickly fill the console up, or make the application slow). That also means you need to modify the ASP.NET browser detection, as it wrongly identifies Edge as Chrome.

This can be done in ASP.NET by writing a HTTP-module which runs on every request, that appends a http-header for every response, depending on the request's referrer. For Chrome, it needs to add Content-Security-Policy.

// https://stackoverflow.com/questions/31870789/check-whether-browser-is-chrome-or-edgepublic class BrowserInfo{    public System.Web.HttpBrowserCapabilities Browser { get; set; }    public string Name { get; set; }    public string Version { get; set; }    public string Platform { get; set; }    public bool IsMobileDevice { get; set; }    public string MobileBrand { get; set; }    public string MobileModel { get; set; }    public BrowserInfo(System.Web.HttpRequest request)    {        if (request.Browser != null)        {            if (request.UserAgent.Contains("Edge")                && request.Browser.Browser != "Edge")            {                this.Name = "Edge";            }            else            {                this.Name = request.Browser.Browser;                this.Version = request.Browser.MajorVersion.ToString();            }            this.Browser = request.Browser;            this.Platform = request.Browser.Platform;            this.IsMobileDevice = request.Browser.IsMobileDevice;            if (IsMobileDevice)            {                this.Name = request.Browser.Browser;            }        }    }}void context_EndRequest(object sender, System.EventArgs e){    if (System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Response != null)    {        System.Web.HttpResponse response = System.Web.HttpContext.Current.Response;        try        {            // response.Headers["P3P"] = "CP=\\\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\\\"":            // response.Headers.Set("P3P", "CP=\\\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\\\"");            // response.AddHeader("P3P", "CP=\\\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\\\"");            response.AppendHeader("P3P", "CP=\\\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\\\"");            // response.AppendHeader("X-Frame-Options", "DENY");            // response.AppendHeader("X-Frame-Options", "SAMEORIGIN");            // response.AppendHeader("X-Frame-Options", "AllowAll");            if (System.Web.HttpContext.Current.Request.UrlReferrer != null)            {                // "X-Frame-Options": "ALLOW-FROM " Not recognized in Chrome                 string host = System.Web.HttpContext.Current.Request.UrlReferrer.Scheme + System.Uri.SchemeDelimiter                            + System.Web.HttpContext.Current.Request.UrlReferrer.Authority                ;                string selfAuth = System.Web.HttpContext.Current.Request.Url.Authority;                string refAuth = System.Web.HttpContext.Current.Request.UrlReferrer.Authority;                // SQL.Log(System.Web.HttpContext.Current.Request.RawUrl, System.Web.HttpContext.Current.Request.UrlReferrer.OriginalString, refAuth);                if (IsHostAllowed(refAuth))                {                    BrowserInfo bi = new BrowserInfo(System.Web.HttpContext.Current.Request);                    // bi.Name = Firefox                    // bi.Name = InternetExplorer                    // bi.Name = Chrome                    // Chrome wants entire path...                     if (!System.StringComparer.OrdinalIgnoreCase.Equals(bi.Name, "Chrome"))                        response.AppendHeader("X-Frame-Options", "ALLOW-FROM " + host);                        // unsafe-eval: invalid JSON https://github.com/keen/keen-js/issues/394                    // unsafe-inline: styles                    // data: url(data:image/png:...)                    // https://www.owasp.org/index.php/Clickjacking_Defense_Cheat_Sheet                    // https://www.ietf.org/rfc/rfc7034.txt                    // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options                    // https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP                    // https://stackoverflow.com/questions/10205192/x-frame-options-allow-from-multiple-domains                    // https://content-security-policy.com/                    // http://rehansaeed.com/content-security-policy-for-asp-net-mvc/                    // This is for Chrome:                    // response.AppendHeader("Content-Security-Policy", "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: *.msecnd.net vortex.data.microsoft.com " + selfAuth + " " + refAuth);                    System.Collections.Generic.List<string> ls = new System.Collections.Generic.List<string>();                    ls.Add("default-src");                    ls.Add("'self'");                    ls.Add("'unsafe-inline'");                    ls.Add("'unsafe-eval'");                    ls.Add("data:");                    // http://az416426.vo.msecnd.net/scripts/a/ai.0.js                    // ls.Add("*.msecnd.net");                    // ls.Add("vortex.data.microsoft.com");                    ls.Add(selfAuth);                    ls.Add(refAuth);                    string contentSecurityPolicy = string.Join(" ", ls.ToArray());                    response.AppendHeader("Content-Security-Policy", contentSecurityPolicy);                }                else                {                    response.AppendHeader("X-Frame-Options", "SAMEORIGIN");                }            }            else                response.AppendHeader("X-Frame-Options", "SAMEORIGIN");        }        catch (System.Exception ex)        {            // WTF ?             System.Console.WriteLine(ex.Message); // Suppress warning        }    } // End if (System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Response != null)} // End Using context_EndRequestprivate static string[] s_allowedHosts = new string[] {     "localhost:49533"    ,"localhost:52257"    ,"vmcompany1"    ,"vmcompany2"    ,"vmpostalservices"    ,"example.com"};public static bool IsHostAllowed(string host){    return Contains(s_allowedHosts, host);} // End Function IsHostAllowed public static bool Contains(string[] allowed, string current){    for (int i = 0; i < allowed.Length; ++i)    {        if (System.StringComparer.OrdinalIgnoreCase.Equals(allowed[i], current))            return true;    } // Next i     return false;} // End Function Contains 

You need to register the context_EndRequest function in the HTTP-module Init function.

public class RequestLanguageChanger : System.Web.IHttpModule{    void System.Web.IHttpModule.Dispose()    {        // throw new NotImplementedException();    }    void System.Web.IHttpModule.Init(System.Web.HttpApplication context)    {        // https://stackoverflow.com/questions/441421/httpmodule-event-execution-order        context.EndRequest += new System.EventHandler(context_EndRequest);    }    // context_EndRequest Code from above comes here}

Next you need to add the module to your application. You can either do this programmatically in Global.asax by overriding the Init function of the HttpApplication, like this:

namespace ChangeRequestLanguage{    public class Global : System.Web.HttpApplication    {        System.Web.IHttpModule mod = new libRequestLanguageChanger.RequestLanguageChanger();        public override void Init()        {            mod.Init(this);            base.Init();        }        protected void Application_Start(object sender, System.EventArgs e)        {        }        protected void Session_Start(object sender, System.EventArgs e)        {        }        protected void Application_BeginRequest(object sender, System.EventArgs e)        {        }        protected void Application_AuthenticateRequest(object sender, System.EventArgs e)        {        }        protected void Application_Error(object sender, System.EventArgs e)        {        }        protected void Session_End(object sender, System.EventArgs e)        {        }        protected void Application_End(object sender, System.EventArgs e)        {        }    }}

or you can add entries to Web.config if you don't own the application source-code:

      <httpModules>        <add name="RequestLanguageChanger" type= "libRequestLanguageChanger.RequestLanguageChanger, libRequestLanguageChanger" />      </httpModules>    </system.web>  <system.webServer>    <validation validateIntegratedModeConfiguration="false"/>    <modules runAllManagedModulesForAllRequests="true">      <add name="RequestLanguageChanger" type="libRequestLanguageChanger.RequestLanguageChanger, libRequestLanguageChanger" />    </modules>  </system.webServer></configuration>

The entry in system.webServer is for IIS7+, the other in system.web is for IIS 6.
Note that you need to set runAllManagedModulesForAllRequests to true, for that it works properly.

The string in type is in the format "Namespace.Class, Assembly". Note that if you write your assembly in VB.NET instead of C#, VB creates a default-Namespace for each project, so your string will look like

"[DefaultNameSpace.Namespace].Class, Assembly"

If you want to avoid this problem, write the DLL in C#.