14

I have an ASP.NET web API that is being called by three different SPA. I am using windows authentication for the web API. I initially tried to configure CORS in the Web.config like this:

<httpProtocol>
    <customHeaders>
        <add name="Access-Control-Allow-Origin" value="http://localhost:63342" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE" />
        <add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept" />
        <add name="Access-Control-Allow-Credentials" value="true" />
    </customHeaders>
</httpProtocol>

This caused this preflight issue:

Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin (...) is therefore not allowed access.

that I solved by adding the following method in Global.asax.cs:

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

This approach worked perfectly for a single SPA. I thought that I could go to the Web.config and add the other origins like this:

<add name="Access-Control-Allow-Origin" value="http://localhost:63342,http://localhost:63347,http://localhost:63345/>

but apparently that is not allowed. This produced the following error:

The 'Access-Control-Allow-Origin' header contains multiple values (...), but only one is allowed. Origin (...) is therefore not allowed access.

So in order to try and fix this, I changed my approach and instead decided to try to configure CORS on the WebAPIConfig.cs, in the Register method like this:

var cors = new EnableCorsAttribute("http://localhost:63342,http://localhost:63347,http://localhost:63345", "Origin, X-Requested-With, Content-Type, Accept", "GET, POST, PUT, DELETE");
cors.SupportsCredentials = true;
config.EnableCors(cors);

I thought this would work but now I have the preflight error again when using PUT and DELETE requests and I don't know how to fix this. I debugged the Application_BeginRequest method and it is still flushing the OPTIONS request so I have no idea of what is causing this error. Does anyone know how I can solve this issue?

EDIT:

The print of the preflight error:

enter image description here

João Paiva
  • 1,937
  • 3
  • 19
  • 41
  • @Aravind I'm sorry I can't really understand the solution you suggested because SO doubles the quotes for some reason. Could you please write it in another manner somehow? – João Paiva Sep 21 '16 at 14:50
  • You can allow all domains and methods by using `new EnableCorsAttribute("*","*","*");` Better way to handle this is to create your own custom attribute using ICorsPolicyProvider and implementing GetCorsPolicyAsync method. – Paresh Sep 21 '16 at 15:02
  • @Paresh But I can't use the * because I'm using windows authentication and it is incompatible. Besides that I only want to allow those three urls. – João Paiva Sep 21 '16 at 15:12
  • use * for the headers and methods and let the url be as it. new EnableCorsAttribute("http://localhost:63342, http://localhost:63347, http://localhost:63345","*","*"); – Aravind Sep 22 '16 at 06:52
  • @Aravind I tried it but it didn't work. Still getting the preflight error. – João Paiva Sep 26 '16 at 09:43
  • did you try adding the EnableCorsAttribute in controller or a particular method by annotating them there? – Aravind Sep 26 '16 at 09:52
  • @Aravind I tried adding it just now. I specified only one origin to see if the problem would go away but I'm still getting the preflight error. – João Paiva Sep 26 '16 at 10:12
  • please add a screenshot of what error you get ! to try out some other alternative option as none of the available ones are not working! – Aravind Sep 26 '16 at 10:25
  • @Aravind I added it to the question. – João Paiva Sep 26 '16 at 10:41

2 Answers2

14

I was able to solve my problem by further customizing the Application_BeginRequest method in Global.asax.cs, like this:

protected void Application_BeginRequest()
{
    if (Request.HttpMethod == "OPTIONS")
    {
        Response.StatusCode = (int)HttpStatusCode.OK;
        Response.AppendHeader("Access-Control-Allow-Origin", Request.Headers.GetValues("Origin")[0]);
        Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
        Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
        Response.AppendHeader("Access-Control-Allow-Credentials", "true");
        Response.End();
    }
}

What this code does is add the missing headers to the OPTIONS response (preflight request) that were causing the preflight error. Since I have different origins calling my web API, I'm using Request.Headers.GetValues("Origin")[0]) to set the origin in the response dinamically.

In the WebApiConfig.cs I still specified the different origins but used wildcards on the headers and methods, as well as setting the SupportsCredentials to true, like this:

var cors = new EnableCorsAttribute("http://localhost:63342,http://localhost:63347,http://localhost:63345", "*", "*");
cors.SupportsCredentials = true;
config.EnableCors(cors);

Also, if you're using AngularJS like I am, you must configure $http to use credentials. This can be configured globally like this:

angular
.module('Application')
.config(['$httpProvider',
    function config($httpProvider) {
        $httpProvider.defaults.withCredentials = true;
    }
]);

And that's it. This solved my problem. If someone else is still having problems, I recommend reading the following publications, which helped me reach my answer:

Community
  • 1
  • 1
João Paiva
  • 1,937
  • 3
  • 19
  • 41
2

Create custom attribute using ICorsPolicyProvider something like following to check if the requested origin is allowed or not

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,AllowMultiple = false)]
    public class EnableCorsForAPIKeysAttribute :
      Attribute, ICorsPolicyProvider, IFilter
    {
        public async Task<CorsPolicy> GetCorsPolicyAsync(
          HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var corsRequestContext = request.GetCorsRequestContext();
            var originRequested = corsRequestContext.Origin;
            if (await IsValidOrigin(originRequested)) //Check if requested origin is valid or not
            {
                // Grant CORS request
                var policy = new CorsPolicy
                {
                    AllowAnyHeader = true,
                    AllowAnyMethod = true
                };
                policy.Origins.Add(originRequested);
                return policy;
            }
            else
            {
                // Reject CORS request
                return null;
            }
        }

        public bool AllowMultiple { get {return false;} }
    }

To use it, add it to your API controller

[EnableCorsForAPIKeys]
public class APIBaseController : ApiController
{
}
Paresh
  • 993
  • 1
  • 7
  • 15
  • Wouldn't this solution make me have to add that annotation to all the controllers? I would rather have a solution where I can configure everything globally. – João Paiva Sep 26 '16 at 09:50
  • I think you can do it at the global level using `config.Filters.Add(new EnableCorsForAPIKeysAttribute());` – Paresh Sep 26 '16 at 16:21