Access-control-allow-origin with multiple domains
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 ?
Solution 1:
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 "-").
Solution 2:
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:
- Grabs the
Origin
request header. - Checks if the origin value is one of the whitelisted values.
- 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");
}
Solution 3:
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
}
Solution 4:
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.