109

In my web.config I would like to specify more than one domain for the access-control-allow-origin directive. I don't want to use *. I've tried this syntax:

<add name="Access-Control-Allow-Origin" value="http://localhost:1506, http://localhost:1502" />

this one

<add name="Access-Control-Allow-Origin" value="http://localhost:1506 http://localhost:1502" />

this one

<add name="Access-Control-Allow-Origin" value="http://localhost:1506; http://localhost:1502" />

and this one

<add name="Access-Control-Allow-Origin" value="http://localhost:1506" />
<add name="Access-Control-Allow-Origin" value="http://localhost:1502" />

but none of them work. What is the correct syntax ?

Michael
  • 5,910
  • 4
  • 52
  • 74
Sam
  • 12,806
  • 23
  • 89
  • 164

12 Answers12

99

For IIS 7.5+ and Rewrite 2.0 you can use:

<system.webServer>
   <httpProtocol>
     <customHeaders>
         <add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept" />
         <add name="Access-Control-Allow-Methods" value="POST,GET,OPTIONS,PUT,DELETE" />
     </customHeaders>
   </httpProtocol>
        <rewrite>            
            <outboundRules>
                <clear />                
                <rule name="AddCrossDomainHeader">
                    <match serverVariable="RESPONSE_Access_Control_Allow_Origin" pattern=".*" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="true">
                        <add input="{HTTP_ORIGIN}" pattern="(http(s)?://((.+\.)?domain1\.com|(.+\.)?domain2\.com|(.+\.)?domain3\.com))" />
                    </conditions>
                    <action type="Rewrite" value="{C:0}" />
                </rule>           
            </outboundRules>
        </rewrite>
 </system.webServer>

Explaining the server variable RESPONSE_Access_Control_Allow_Origin portion:
In Rewrite you can use any string after RESPONSE_ and it will create the Response Header using the rest of the word as the header name (in this case Access-Control-Allow-Origin). Rewrite uses underscores "_" instead of dashes "-" (rewrite converts them to dashes)

Explaining the server variable HTTP_ORIGIN :
Similarly, in Rewrite you can grab any Request Header using HTTP_ as the prefix. Same rules with the dashes (use underscores "_" instead of dashes "-").

Michael
  • 5,910
  • 4
  • 52
  • 74
Paco Zarate
  • 1,665
  • 13
  • 13
  • Can you think of any reasons why this wouldn't work with IIS 7.5? – Phil Ricketts Jul 03 '15 at 10:06
  • I think it should work. I specified the IIS 8.5 version because it is where i tested it. – Paco Zarate Jul 07 '15 at 07:47
  • 4
    @PacoZarate Nice one, great tip. To simplify to regex, and make it more generic, you can use - `(http(s)?:\/\/((.+\.)?(domain1|domain2)\.(com|org|net)))`. That way you can add other domains fairly easy and support multiple Top-Level domains (e.g com, org, net etc.). – Merlin Nov 11 '15 at 06:35
  • 4
    Just tried this in IIS 7.5. Seems to be working just fine. – Prescient Nov 25 '15 at 17:17
  • Yep, after installing URL Rewrite 2.0, using Paco code in web.config and adding it finally worked! – graumanoz Aug 18 '16 at 08:14
  • @PacoZarate I tried this got net::ERR_CONNECTION_RESET message. Can you help? I'm using Chrome. – NKD Oct 20 '16 at 20:36
  • @NKD i would suggest you to create a new SO question and place the relevant information to reproduce it, including if you are setting up https (or just http) and how many domains you are trying to handle. Also please include the relevant parts of the web.config replacing the actual domainnames. then place a link here – Paco Zarate Oct 20 '16 at 22:29
  • @PacoZarate I took your advice and asked a new questions here with the details: http://stackoverflow.com/questions/40182950/configure-url-rewrite-using-server-variable-to-support-multiple-origins – NKD Oct 21 '16 at 18:10
  • @PacoZarate this isn't working for me, and I believe it has to do with the fact that you need to configure the AllowedServerVariables in IIS UI first, otherwise this doesn't work. And in my case, that is not possible as I am hosting on Azure web app, and don't have access to IIS. – crichavin Dec 30 '16 at 01:20
  • This answer is better than the accepted one for people using Rewrite 2.0. It's really handy to be able to keep the domain whitelist in the web.config instead of the app layer. – 96khz Oct 16 '17 at 13:08
  • Does anyone have an example of what support for third-level domains would look like? (i.e. mycustomdomain.mywebsite.com) - or will this expression cover that? Trying this out of the box for me didn't work in this case. – Dave Cole Jan 16 '18 at 19:41
  • Edit: Looks like @PacoZarate 's solution work the best. The one in the answer lacks backslashes before the forward slashes and didn't work on my server. – Dave Cole Jan 16 '18 at 20:03
  • 2
    Having trouble with caching? After tweaking the web.config, the first website I go to matches fine, but the second returns the same header as the first. Thus causing the domains not too match. – Airn5475 Mar 28 '18 at 18:41
  • @Airn5475, did you manage to solve this issue? We have exactly the same problem. – Christiaan Jul 19 '18 at 09:04
  • One thing that caught me out for 2 hours when implementing this and not seeing it working, was Chrome doing some caching. In the developer tools you can disable caching, but I think a CTRL+F5 will do the job. – Robin Jun 08 '20 at 17:04
  • Would this work for a hosted service and adding the section to the app.config file? –  Jun 29 '20 at 23:21
82

There can only be one Access-Control-Allow-Origin response header, and that header can only have one origin value. Therefore, in order to get this to work, you need to have some code that:

  1. Grabs the Origin request header.
  2. Checks if the origin value is one of the whitelisted values.
  3. If it is valid, sets the Access-Control-Allow-Origin header with that value.

I don't think there's any way to do this solely through the web.config.

if (ValidateRequest()) {
    Response.Headers.Remove("Access-Control-Allow-Origin");
    Response.AddHeader("Access-Control-Allow-Origin", Request.UrlReferrer.GetLeftPart(UriPartial.Authority));

    Response.Headers.Remove("Access-Control-Allow-Credentials");
    Response.AddHeader("Access-Control-Allow-Credentials", "true");

    Response.Headers.Remove("Access-Control-Allow-Methods");
    Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
}
Crispy Ninja
  • 338
  • 4
  • 11
monsur
  • 39,509
  • 15
  • 93
  • 91
  • 3
    That answers my question. I'm not sure why Microsoft does not allow specifying multiple origins in the web.config though.... – Sam Jun 27 '13 at 06:56
  • 17
    Where can I add this code? I have plain text files generated by server and read via AJAX, no code at all. Where can I put the code to restrict access to text files in my directory? – Harry Feb 05 '14 at 12:58
  • 4
    @Simon_Weaver there is a `*` value that allows any origin to access the resource. However the original question was asking about whitelisting a set of domains. – monsur Apr 29 '15 at 02:15
  • 2
    as i am new to asp .net can i ask where can i put this code in my asp .net web api project? – Amrit Oct 31 '18 at 13:02
  • why you take from Referrer header instead of Origin header ? – zb' May 10 '21 at 03:39
22

In Web.API this attribute can be added using Microsoft.AspNet.WebApi.Cors as detailed at http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api

In MVC you could create a filter attribute to do this work for you:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
                AllowMultiple = true, Inherited = true)]
public class EnableCorsAttribute : FilterAttribute, IActionFilter {
    private const string IncomingOriginHeader = "Origin";
    private const string OutgoingOriginHeader = "Access-Control-Allow-Origin";
    private const string OutgoingMethodsHeader = "Access-Control-Allow-Methods";
    private const string OutgoingAgeHeader = "Access-Control-Max-Age";

    public void OnActionExecuted(ActionExecutedContext filterContext) {
        // Do nothing
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var isLocal = filterContext.HttpContext.Request.IsLocal;
        var originHeader = 
             filterContext.HttpContext.Request.Headers.Get(IncomingOriginHeader);
        var response = filterContext.HttpContext.Response;

        if (!String.IsNullOrWhiteSpace(originHeader) &&
            (isLocal || IsAllowedOrigin(originHeader))) {
            response.AddHeader(OutgoingOriginHeader, originHeader);
            response.AddHeader(OutgoingMethodsHeader, "GET,POST,OPTIONS");
            response.AddHeader(OutgoingAgeHeader, "3600");
        }
    }

    protected bool IsAllowedOrigin(string origin) {
        // ** replace with your own logic to check the origin header
        return true;
    }
}

Then either enable it for specific actions / controllers:

[EnableCors]
public class SecurityController : Controller {
    // *snip*
    [EnableCors]
    public ActionResult SignIn(Guid key, string email, string password) {

Or add it for all controllers in Global.asax.cs

protected void Application_Start() {
    // *Snip* any existing code

    // Register global filter
    GlobalFilters.Filters.Add(new EnableCorsAttribute());
    RegisterGlobalFilters(GlobalFilters.Filters);

    // *snip* existing code
}
Rob Church
  • 6,233
  • 2
  • 36
  • 46
  • Do you know what versions of .Net / MVC this works for? – Keab42 Jul 14 '15 at 09:29
  • I'm using this successfully in .net 4 / MVC 3 - as far as I'm aware it should work in higher versions but there may be a preferred way of registering the global filter in later MVC versions. – Rob Church Jul 15 '15 at 13:03
  • just please note its WEB API 2 solution only. not for WEB API 1. – Samih A Mar 24 '17 at 13:29
5

After reading every answer and trying them, none of them helped me. What I found while searching elsewhere is that you can create a custom attribute that you can then add to your controller. It overwrites the EnableCors ones and add the whitelisted domains in it.

This solution is working well because it lets you have the whitelisted domains in the webconfig (appsettings) instead of harcoding them in the EnableCors attribute on your controller.

 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class EnableCorsByAppSettingAttribute : Attribute, ICorsPolicyProvider
{
    const string defaultKey = "whiteListDomainCors";
    private readonly string rawOrigins;
    private CorsPolicy corsPolicy;

    /// <summary>
    /// By default uses "cors:AllowedOrigins" AppSetting key
    /// </summary>
    public EnableCorsByAppSettingAttribute()
        : this(defaultKey) // Use default AppSetting key
    {
    }

    /// <summary>
    /// Enables Cross Origin
    /// </summary>
    /// <param name="appSettingKey">AppSetting key that defines valid origins</param>
    public EnableCorsByAppSettingAttribute(string appSettingKey)
    {
        // Collect comma separated origins
        this.rawOrigins = AppSettings.whiteListDomainCors;
        this.BuildCorsPolicy();
    }

    /// <summary>
    /// Build Cors policy
    /// </summary>
    private void BuildCorsPolicy()
    {
        bool allowAnyHeader = String.IsNullOrEmpty(this.Headers) || this.Headers == "*";
        bool allowAnyMethod = String.IsNullOrEmpty(this.Methods) || this.Methods == "*";

        this.corsPolicy = new CorsPolicy
        {
            AllowAnyHeader = allowAnyHeader,
            AllowAnyMethod = allowAnyMethod,
        };

        // Add origins from app setting value
        this.corsPolicy.Origins.AddCommaSeperatedValues(this.rawOrigins);
        this.corsPolicy.Headers.AddCommaSeperatedValues(this.Headers);
        this.corsPolicy.Methods.AddCommaSeperatedValues(this.Methods);
    }

    public string Headers { get; set; }
    public string Methods { get; set; }

    public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request,
                                               CancellationToken cancellationToken)
    {
        return Task.FromResult(this.corsPolicy);
    }
}

    internal static class CollectionExtensions
{
    public static void AddCommaSeperatedValues(this ICollection<string> current, string raw)
    {
        if (current == null)
        {
            return;
        }

        var paths = new List<string>(AppSettings.whiteListDomainCors.Split(new char[] { ',' }));
        foreach (var value in paths)
        {
            current.Add(value);
        }
    }
}

I found this guide online and it worked like a charm :

http://jnye.co/Posts/2032/dynamic-cors-origins-from-appsettings-using-web-api-2-2-cross-origin-support

I thought i'd drop that here for anyone in need.

Helpha
  • 402
  • 5
  • 15
3

I managed to solve this in the Request handling code following advice from 'monsur'.

string origin = WebOperationContext.Current.IncomingRequest.Headers.Get("Origin");

WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", origin);
abatishchev
  • 92,232
  • 78
  • 284
  • 421
bsandhu
  • 487
  • 4
  • 5
  • That's the way to do in webform for example. Simply use Request.Headers when available. And, if needed, use a whitelist to filter allowed domains only. – AFract Mar 23 '15 at 11:19
  • 3
    This is as good as adding in the web.config file – Isaiah4110 Sep 11 '15 at 16:36
3

For IIS 7.5+ you can use IIS CORS Module: https://www.iis.net/downloads/microsoft/iis-cors-module

Your web.config should be something like this:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <cors enabled="true" failUnlistedOrigins="true">
            <add origin="http://localhost:1506">
                <allowMethods>                    
                    <add method="GET" />
                    <add method="HEAD" />
                    <add method="POST" />
                    <add method="PUT" /> 
                    <add method="DELETE" /> 
                </allowMethods>
            </add>
            <add origin="http://localhost:1502">
                <allowMethods>
                    <add method="GET" />
                    <add method="HEAD" />
                    <add method="POST" />
                    <add method="PUT" /> 
                    <add method="DELETE" /> 
                </allowMethods>
            </add>
        </cors>
    </system.webServer>
</configuration>

You can find the configuration reference in here: https://docs.microsoft.com/en-us/iis/extensions/cors-module/cors-module-configuration-reference

Mario Arturo
  • 191
  • 1
  • 8
2

Look into the Thinktecture IdentityModel library -- it has full CORS support:

http://brockallen.com/2012/06/28/cors-support-in-webapi-mvc-and-iis-with-thinktecture-identitymodel/

And it can dynamically emit the ACA-Origin you want.

Brock Allen
  • 7,315
  • 17
  • 24
1

You can add this code to your asp.net webapi project

in file Global.asax

    protected void Application_BeginRequest()
{
    string origin = Request.Headers.Get("Origin");
    if (Request.HttpMethod == "OPTIONS")
    {
        Response.AddHeader("Access-Control-Allow-Origin", origin);
        Response.AddHeader("Access-Control-Allow-Headers", "*");
        Response.AddHeader("Access-Control-Allow-Methods", "GET,POST,PUT,OPTIONS,DELETE");
        Response.StatusCode = 200;
        Response.End();
    }
    else
    {
        Response.AddHeader("Access-Control-Allow-Origin", origin);
        Response.AddHeader("Access-Control-Allow-Headers", "*");
        Response.AddHeader("Access-Control-Allow-Methods", "GET,POST,PUT,OPTIONS,DELETE");
    }
}
Jackdon Wang
  • 101
  • 1
  • 4
1

Try this:

<add name="Access-Control-Allow-Origin" value="['URL1','URL2',...]" />

Volodymyr_UA
  • 116
  • 5
1

I have had luck with the CORS IIS add-in which you can download from Microsoft. It supports multiple domains, it allows different authentication configurations, and it allows you to only offer a subset of APIs to different domains if you choose to to get fancy.

You just need to add in a section like this in your web.config.

  <system.webServer>
    <cors enabled="true" failUnlistedOrigins="true">
      <add origin="http://server1.com"
              allowCredentials="true"
              allowed="true"
              maxAge="120">
      </add>
      <add origin="http://server2.com"
              allowed="true"
              allowCredentials="true"
              maxAge="120">
      </add>
    </cors>
  </system.webServer>

If you want to dive into the options look here.

One thing to note that threw me off at first was that this conflicts with other web.config tweaks like manually adding the Access-Control-Origin header yourself, so only do one or the other; not both.

The other thing to note is that even if you have the server setup perfectly, you may need client side tweaks to actually consume it. For example, here are the Javascript fetch method options that needed to be used to call methods against the CORS server with authentication.

fetch(url, {
       method: 'GET', // *GET, POST, PUT, DELETE, etc.
       mode: 'cors', // no-cors, *cors, same-origin
       cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
       credentials: 'include', // include, *same-origin, omit
   })

Good luck.

Michael
  • 5,910
  • 4
  • 52
  • 74
0

You can use owin middleware to define cors policy in which you can define multiple cors origins

return new CorsOptions
        {
            PolicyProvider = new CorsPolicyProvider
            {
                PolicyResolver = context =>
                {
                    var policy = new CorsPolicy()
                    {
                        AllowAnyOrigin = false,
                        AllowAnyMethod = true,
                        AllowAnyHeader = true,
                        SupportsCredentials = true
                    };
                    policy.Origins.Add("http://foo.com");
                    policy.Origins.Add("http://bar.com");
                    return Task.FromResult(policy);
                }
            }
        };
-3

You only need:

  • add a Global.asax to your project,
  • delete <add name="Access-Control-Allow-Origin" value="*" /> from your web.config.
  • afterward, add this in the Application_BeginRequest method of Global.asax:

    HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin","*");
    
    if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
    {
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept");
        HttpContext.Current.Response.End();
    }
    

I hope this help. that work for me.

Michael
  • 5,910
  • 4
  • 52
  • 74
  • Adding "...-Origin: *" works except for when you allow credentials. If you have the allow-credentials set to true, then you have to specify a domain (not simply *). That's where the crux of this problem lies. Otherwise, you could just specify "...allow-credentials: false" and be done with it. – Richard Sep 25 '13 at 14:01