Handling CORS Preflight requests to ASP.NET MVC actions

Solution 1:

So I have found a solution that works. For each request, I check whether it's a CORS request & whether the request is coming in with the OPTIONS verb, indicating that it's the preflight request. If it is, I just send an empty response back (which only contains the headers configured in IIS of course), thus negating the controller action execution.

Then if the client confirms it's allowed to perform the request based on the returned headers from preflight, the actual POST is performed & the controller action is executed. And example of my code:

protected void Application_BeginRequest()
{
    if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
        Request.HttpMethod == "OPTIONS") {
        Response.Flush();
    }
}

As mentioned, this worked for me, but if anyone knows of a better way, or of any flaws in my current implementation, I would appreciate to hear about them.

Solution 2:

expanding on Carl's answer, i took his code and plugged it into my OWIN pipeline:

app.Use((context, next) =>
{
     if (context.Request.Headers.Any(k => k.Key.Contains("Origin")) && context.Request.Method == "OPTIONS")
     {
         context.Response.StatusCode = 200;
         return context.Response.WriteAsync("handled");
     }

     return next.Invoke();
});

Just add this to the beginning (or anywhere before you register the WebAPI) of your IAppBuilder in Startup.cs

Solution 3:

The accepted answer works like a charm, but I found that the request was actually being passed down to the controller. I was receiving a 200 status code, but the response body contained a lot of HTML with an exception from the controller. So instead of using Response.Flush(), I found it was better to use Response.End(), which does stop the execution of the request. This alternative solution would look like this:

EDIT: fixed a typo carried from the original answer.

protected void Application_BeginRequest()
{
    if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
        Request.HttpMethod == "OPTIONS") {
        Response.End();
    }
}

Solution 4:

Here is how I handled the preflight/CORS issues with ASP.Net Web Api. I simply added the Microsoft.AspNet.WebApi.Cors Nuget package to my Web project.Then in my WebApiConfig.cs file I added this line:

config.EnableCors(new ApplicationCorsPolicy());

and created a custom PolicyProvider class

public class ApplicationCorsPolicy : Attribute, ICorsPolicyProvider
{
    public async Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var corsRequestContext = request.GetCorsRequestContext();
        var originRequested = corsRequestContext.Origin;

        if (await IsOriginFromAPaidCustomer(originRequested))
        {
            // Grant CORS request
            var policy = new CorsPolicy
            {
                AllowAnyHeader = true,
                AllowAnyMethod = true
            };
            policy.Origins.Add(originRequested);
            return policy;
        }
        // Reject CORS request
        return null;
    }

    private async Task<bool> IsOriginFromAPaidCustomer(string originRequested)
    {
        // Do database look up here to determine if origin should be allowed.
        // In my application I have a table that has a list of domains that are
        // allowed to make API requests to my service. This is validated here.
        return true;
    }
}

See, the Cors framework allows you to add your own logic for determining which origins are allowed, etc. This is very helpful if you are exposing a REST API to the outside world and the list of people (origins) who can access your site are in a controlled environment like a database. Now, if you are simply allowing all origins (which might not be such a good idea in all cases) you can just do this in WebApiConfig.cs to enable CORS globally:

config.EnableCors();

Just like Filters and Handlers in WebApi you can also add class or method level annotations to your controllers like so:

[EnableCors("*, *, *, *")]

Note that the EnableCors attribute has a constructor that accepts the following parameters

  1. List of Origins Allowed
  2. List of request headers allowed
  3. List of HTTP methods allowed
  4. List of response headers allowed

You can specify statically at each controller/end point who is allowed to access what resource.

Update 06/24/2016: I should mention that I have the following in my Web.config. It looks like these may not be the defaults for everyone.

<system.webServer>
    <handlers>
        <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
        <remove name="OPTIONSVerbHandler" />
        <remove name="TRACEVerbHandler" />
        <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
        </handlers>
</system.webServer>

Source: Microsoft

Solution 5:

None of these answers worked for me, but the following webconfig settings did. The two key settings for me were setting Access-Control-Allow-Headers to Content-Type and commenting out the line that removes the OPTIONSVerbHandler:

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"></modules>
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type" />
      </customHeaders>
    </httpProtocol>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <!--<remove name="OPTIONSVerbHandler" />-->
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>