Reputation: 1547
I have a WebAPI controller with a custom CORS policy provider attribute on the class. In defining the attribute, I have the following constructor.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class ConfiguredCORSPolicyProviderAttribute : ActionFilterAttribute, ICorsPolicyProvider
{
private CorsPolicy _policy;
public ConfiguredCORSPolicyProviderAttribute()
{
_policy = new CorsPolicy
{
AllowAnyMethod = true,
AllowAnyHeader = true
};
// If there are no domains in the 'CORSDomainSection' section in Web.config, all origins will be allowed by default.
var domains = (CORSDomainSection)ConfigurationManager.GetSection("CORSDomainSection");
if (domains != null)
{
foreach (DomainConfigElement domain in domains.Domains)
{
_policy.Origins.Add(domain.Domain);
}
}
else
{
_policy.AllowAnyOrigin = true;
}
}
public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request)
{
return Task.FromResult(_policy);
}
public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken token)
{
return GetCorsPolicyAsync(request);
}
}
The ConfigurationManager gets a list (from Web.config) of acceptable origins/domains that I want to allow to make requests.
This code appropriately handles the "Access-Control-Allow-Origin" header, adding it when the request origin is on the list, and withholding it when not. However, the code in the controller still gets called no matter what.
Why, and how do I appropriately prevent the controller from executing if the origin of the request isn't allowed?
UPDATE || [April 12, 2016 @ 12:30p]
I was able to resolve the issue using a combination of OnActionExecuted
and OnActionExecuting
method overrides, code below.
/// <summary>
/// Executed after the action method is invoked.
/// </summary>
/// <param name="context">The context of the HTTP request.</param>
public override void OnActionExecuted(HttpActionExecutedContext context)
{
string requestOrigin;
try
{
requestOrigin = context.Request.Headers.GetValues("Origin").FirstOrDefault();
}
catch
{
requestOrigin = string.Empty;
}
if (IsAllowedOrigin(requestOrigin))
{
context.Response.Headers.Add("Access-Control-Allow-Origin", requestOrigin);
if (IsPreflight(context))
{
string allowedMethods = string.Empty;
string allowedHeaders = string.Empty;
if (Policy.AllowAnyMethod)
{
allowedMethods = context.Request.Headers.GetValues("Access-Control-Request-Method").FirstOrDefault();
}
else
{
foreach (var method in Policy.Methods)
{
if (Policy.Methods.IndexOf(method) == 0)
{
allowedMethods = method;
}
else
{
allowedMethods += string.Format(", {0}", method);
}
}
}
try
{
if (Policy.AllowAnyHeader)
{
allowedHeaders = context.Request.Headers.GetValues("Access-Control-Request-Headers").FirstOrDefault();
}
else
{
foreach (var header in Policy.Headers)
{
if (Policy.Headers.IndexOf(header) == 0)
{
allowedHeaders = header;
}
else
{
allowedHeaders += string.Format(", {0}", header);
}
}
}
context.Response.Headers.Add("Access-Control-Allow-Headers", allowedHeaders);
}
catch
{
// Do nothing.
}
context.Response.Headers.Add("Access-Control-Allow-Methods", allowedMethods);
}
}
base.OnActionExecuted(context);
}
/// <summary>
/// Executed before the action method is invoked.
/// </summary>
/// <param name="context">The context of the HTTP request.</param>
public override void OnActionExecuting(HttpActionContext context)
{
string requestOrigin;
try
{
requestOrigin = context.Request.Headers.GetValues("Origin").FirstOrDefault();
}
catch
{
requestOrigin = string.Empty;
}
if (IsAllowedOrigin(requestOrigin))
{
base.OnActionExecuting(context);
}
else
{
context.ModelState.AddModelError("State", "The origin of the request is forbidden from making requests.");
context.Response = context.Request.CreateErrorResponse(HttpStatusCode.Forbidden, context.ModelState);
}
}
private bool IsAllowedOrigin(string requestOrigin)
{
requestOrigin = requestOrigin.Replace("https://", "").Replace("http://", "");
if (System.Diagnostics.Debugger.IsAttached || PolicyContains(requestOrigin))
{
return true;
}
else
{
return false;
}
}
private bool PolicyContains(string requestOrigin)
{
foreach (var domain in _policy.Origins)
{
if (domain.Replace("https://", "").Replace("http://", "") == requestOrigin)
{
return true;
}
}
return false;
}
private bool IsPreflight(HttpActionExecutedContext context)
{
string header = string.Empty;
try
{
header = context.Request.Headers.GetValues("Access-Control-Request-Method").FirstOrDefault();
}
catch
{
return false;
}
if (header != null && context.Request.Method == HttpMethod.Options)
{
return true;
}
else
{
return false;
}
}
Upvotes: 3
Views: 1800
Reputation: 100620
CORS headers are not expected to prevent calls to controller - i.e. if native client (pretty much anything that is not a browser) calls the method it should be handled by default.
If you really need to block such calls - perform similar checks in before controller get called in OnActionExecutingAsync
.
Upvotes: 1