EnableCors attribute not adding Access-Control-Allow-Origin to API responses

Viewed 4701

The problem:

For some reason, when enabling CORS, the Access-Control-Allow-Origin header is not included in the response.

The error:

Access to XMLHttpRequest at 'https://.../api/endpoints/some-path' from origin 'https://some.site.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Configuration (.NET MVC 5)

web.config headers:

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="Access-Control-Allow-Credentials" value="true"/>
      <add name="Access-Control-Allow-Headers" value="Content-Type" />
      <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, OPTIONS" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

Web API config:

config.EnableCors();

API endpoint:

[RoutePrefix("api/endpoints")]
[EnableCors(origins: "https://some.site.com", headers: "*", methods: "*")]
public class MyApiController : ApiController
{
    [HttpPost]
    [Route("some-path")]
    [EnableCors(origins: "https://some.site.com", headers: "*", methods: "*")]
    public ResponseModel GetSomeResponse(DataModel model) { ... }
}

global.asax.cs

protected void Application_BeginRequest(object sender, EventArgs e)
{
    if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
        HttpContext.Current.Response.Flush();
}

The only way I can get successfully get the application to return that header is to explicitly include it as a custom header in the web.config:

<add name="Access-Control-Allow-Origin" value="https://some.site.com" />
7 Answers

Add same method with HttpOptions header and same route name. Browser will send preflight request to your controller. Which means Options method will be triggered to access header informations of the server.

[HttpOptions]
[Route("some-path")]
[EnableCors(origins: "https://some.site.com", headers: "*", methods: "*")]
public ResponseModel GetSomeResponse(DataModel model) { ... }

From: https://docs.microsoft.com/en-us/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api

Confirm that the Access-Control-Request-Method and Access-Control-Request-Headers headers are sent with the request and that OPTIONS headers reach the app through IIS.

To configure IIS to allow an ASP.NET app to receive and handle OPTION requests, add the following configuration to the app's web.config file in the <system.webServer> section:

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

The removal of OPTIONSVerbHandler prevents IIS from handling OPTIONS requests. The replacement of ExtensionlessUrlHandler-Integrated-4.0 allows OPTIONS requests to reach the app because the default module registration only allows GET, HEAD, POST, and DEBUG requests with extensionless URLs.

This is working for me. Handling pre-flight request and cors Web.config

<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>

Global.asax.cs

protected void Application_BeginRequest(object sender, EventArgs e)
        {
            if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
            {
                //These headers are handling the "pre-flight" OPTIONS call sent by the browser
                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "*"); // For All HttpVerb
                //HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
                //HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept");
                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "*");
                //HttpContext.Current.Response.AddHeader("Access-Control-Allow‌​-Credentials", "true");
                HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "600");//1728000
                HttpContext.Current.Response.StatusCode = 204;

                HttpContext.Current.Response.End();
            }
        }

WebApiConfig.cs

public static void Register(HttpConfiguration config)
{
    config.EnableCors();
    ....
}

Add Attribuites in your controller

[EnableCors(origins: "*", headers: "*", methods: "*")]

Always try to keep your code simple and clear if possible.

Actually, There's no need to change the web.config, controllers or global.asax.cs. So clean them and Just use the following configuration inside Configure method in Startup.cs file :

app.UseExceptionHandler(builder =>
{
    builder.Run(
    async context =>
    {
        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
        context.Response.Headers.Add("Access-Control-Allow-Origin", "*");

        var error = context.Features.Get<IExceptionHandlerFeature>();
        if (error != null)
        {
            context.Response.AddApplicationError(error.Error.Message);
            await context.Response.WriteAsync(error.Error.Message).ConfigureAwait(false);
        }
    });
});

Where ResponseExtensions is defined as extension with the following details:

public static class ResponseExtensions
{
    public static void AddApplicationError(this HttpResponse response, string message)
    {
        response.Headers.Add("Application-Error", message);
        // CORS
        response.Headers.Add("access-control-expose-headers", "Application-Error");
    }
}

This is working fine in my fully API-based Asp.net project.

Set CORS globally for all the controllers. Inside WebApiConfig class, Create the instance of EnableCorsAttribute and use in EnableCors method as:

var cors = new EnableCorsAttribute("https://some.site.com", "*", "*");
config.EnableCors(cors);

Globally Enable CORS The method described can also be used to enable CORS across the API without annotating each controller as per below in WebApiConfig

public static void Register(HttpConfiguration config)
{
    var corsAttr = new EnableCorsAttribute("http://example.com", "*", "*");
    config.EnableCors(corsAttr);
}
Related