86

I tried to follow the steps at http://enable-cors.org/server_aspnet.html to have my RESTful API (implemented with ASP.NET WebAPI2) work with cross origin requests (CORS Enabled). It's not working unless I modify the web.config.

I installed WebApi Cors dependency:

install-package Microsoft.AspNet.WebApi.Cors -ProjectName MyProject.Web.Api

Then in my App_Start I've got the class WebApiConfig as follows:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var corsAttr = new EnableCorsAttribute("*", "*", "*");
        config.EnableCors(corsAttr);

        var constraintsResolver = new DefaultInlineConstraintResolver();

        constraintsResolver.ConstraintMap.Add("apiVersionConstraint", typeof(ApiVersionConstraint));
        config.MapHttpAttributeRoutes(constraintsResolver); 
        config.Services.Replace(typeof(IHttpControllerSelector), new NamespaceHttpControllerSelector(config));
        //config.EnableSystemDiagnosticsTracing(); 
        config.Services.Replace(typeof(ITraceWriter), new SimpleTraceWriter(WebContainerManager.Get<ILogManager>())); 
        config.Services.Add(typeof(IExceptionLogger), new SimpleExceptionLogger(WebContainerManager.Get<ILogManager>()));
        config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler()); 
    }
}

but after that I run the application, I request a resource with Fiddler like: http://localhost:51589/api/v1/persons and in the response I cannot see the HTTP headers that I should see such as:

  • Access-Control-Allow-Methods: POST, PUT, DELETE, GET, OPTIONS
  • Access-Control-Allow-Origin: *

Am I missing some step? I have tried with the following annotation on the controller:

[EnableCors(origins: "http://example.com", headers: "*", methods: "*")]

Same result, no CORS enabled.

However, if I add the following in my web.config (without even installing the AspNet.WebApi.Cors dependency) it works:

<system.webServer>

<httpProtocol>
  <!-- THESE HEADERS ARE IMPORTANT TO WORK WITH CORS -->
  <!--
  <customHeaders>
    <add name="Access-Control-Allow-Origin" value="*" />
    <add name="Access-Control-Allow-Methods" value="POST, PUT, DELETE, GET, OPTIONS" />
    <add name="Access-Control-Allow-Headers" value="content-Type, accept, origin, X-Requested-With, Authorization, name" />
    <add name="Access-Control-Allow-Credentials" value="true" />
  </customHeaders>
  -->
</httpProtocol>
<handlers>
  <!-- THESE HANDLERS ARE IMPORTANT FOR WEB API TO WORK WITH  GET,HEAD,POST,PUT,DELETE and CORS-->
  <!--

  <remove name="WebDAV" />
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,PUT,DELETE" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
  <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>

Any help would be much appreciated!

Thank you.

Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
diegosasw
  • 13,734
  • 16
  • 95
  • 159
  • 1
    I had the same problem, but I decided to set the Access-Control-Allow-Origin header directly in the controller: HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK); //add some checks if needed response.Headers.Add("Access-Control-Allow-Origin", "*"); – Veselin Vasilev Apr 01 '15 at 07:34

11 Answers11

101

I've created a pared-down demo project for you.

You can try the above API Link from your local Fiddler to see the headers. Here is an explanation.

Global.ascx

All this does is call the WebApiConfig. It's nothing but code organization.

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        WebApiConfig.Register(GlobalConfiguration.Configuration);
    }
}

WebApiConfig.cs

The key method for your here is the EnableCrossSiteRequests method. This is all that you need to do. The EnableCorsAttribute is a globally scoped CORS attribute.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        EnableCrossSiteRequests(config);
        AddRoutes(config);
    }

    private static void AddRoutes(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "Default",
            routeTemplate: "api/{controller}/"
        );
    }

    private static void EnableCrossSiteRequests(HttpConfiguration config)
    {
        var cors = new EnableCorsAttribute(
            origins: "*", 
            headers: "*", 
            methods: "*");
        config.EnableCors(cors);
    }
}

Values Controller

The Get method receives the EnableCors attribute that we applied globally. The Another method overrides the global EnableCors.

public class ValuesController : ApiController
{
    // GET api/values
    public IEnumerable<string> Get()
    {
        return new string[] { 
            "This is a CORS response.", 
            "It works from any origin." 
        };
    }

    // GET api/values/another
    [HttpGet]
    [EnableCors(origins:"http://www.bigfont.ca", headers:"*", methods: "*")]
    public IEnumerable<string> Another()
    {
        return new string[] { 
            "This is a CORS response. ", 
            "It works only from two origins: ",
            "1. www.bigfont.ca ",
            "2. the same origin." 
        };
    }
}

Web.config

You do not need to add anything special into web.config. In fact, this is what the demo's web.config looks like - it's empty.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
</configuration>

Demo

var url = "https://cors-webapi.azurewebsites.net/api/values"

$.get(url, function(data) {
  console.log("We expect this to succeed.");
  console.log(data);
});

var url = "https://cors-webapi.azurewebsites.net/api/values/another"

$.get(url, function(data) {
  console.log(data);
}).fail(function(xhr, status, text) {
  console.log("We expect this to fail.");
  console.log(status);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • Liked this solution. Is there an equivalent solution for WCF based restful service. – Neeraj Apr 02 '15 at 03:45
  • 1
    @Neeraj There is probably a way to do _everything_ in WCF that we can do in Web API. That said, it's going to take a lot more configuration and code to implement a RESTful service with WCF. The Web API was designed specifically for REST whereas WCF is far more extensible, flexible, and designed more for SOAP. See also: http://stackoverflow.com/questions/9348639/wcf-vs-asp-net-web-api – Shaun Luttin Apr 02 '15 at 16:07
  • 22
    I followed all the steps from your answer and from here: http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api#enable-cors. Tried various combinations and nothing enables CORS except for the `Web.config`->`` stuff like in the question. I'm using latest packages (same as in the question). Any suggestions where to look for the problem? Essentially, for some reason this does not solve the same problem for me. – yǝsʞǝla Apr 07 '15 at 07:36
  • 9
    "Fixed it". For some reason it sets CORS headers in production (on real IIS) but not on localhost when ran from VisualStudio. I don't know why, I guess it's conditional somehow. – yǝsʞǝla Apr 07 '15 at 08:14
  • 2
    On my local IIS, I have separate applications with differing port numbers. Shaun's solution didn't work without the Web.Config patch until....I noticed that Shaun uses `"*"` for the origins in WebApiConfig. So I put the `"*"` in as the origin on the EnableCorsAttribute in the controller and now it works without the WebConfig. My reading of how to specify Origins led me to believe that the port number was invalid but I didn't spot that the "*" was ok. Also, most examples call `config.EnableCors(cors);` without the parameter. for example: (http://enable-cors.org/server_aspnet.html) – Rob Von Nesselrode Apr 16 '15 at 05:32
  • 14
    After wasting 3 hours trying to get it work I simply added the 3 headers to the web config to actually get this to work http://stackoverflow.com/a/21458845/286121 – gyozo kudor Jul 17 '15 at 13:57
  • @gyozokudor Bummer. I wonder why one situation requires the headers whereas another doesn't. Hmm. Do let me know if you ever determine why. – Shaun Luttin Jul 17 '15 at 18:41
  • This doesn't work for me. I tried with wildcard and specified domain, but it didn't work. – Alex Jin Mar 13 '16 at 16:15
  • This solutiondoes not work for me, even on @ShaunLuttin demo server I get no CORS headers, see https://dl.dropboxusercontent.com/u/85292167/forever/CORS.png – Aleksei Poliakov Aug 24 '16 at 09:38
  • @DarkWalker Does the [linked Fiddle](https://jsfiddle.net/shaunluttin/Lbfk2ggu/) work for you? Also, the `/another` path that you are using only works from two origins, so we'd expect it not to work unless you're request from one of those two origins. – Shaun Luttin Aug 24 '16 at 14:12
  • 4
    @ShaunLuttin I think I was wrong and it does acttually work. It seems like the framework checks the origin and sends the header ONLY when the origin is in the list of allowed origins. When the origin is not on the list, the framework sends no header (and I mistakenly interpreted this as wrong behavior, because I was expecting to see allowed origin in the header for any response). I will test this and report the results. – Aleksei Poliakov Aug 24 '16 at 14:57
  • This solution worked for me too. Thank you @Shaun for putting up that source code. – Eric Milliot-Martinez Sep 19 '16 at 20:32
  • `[EnableCors(origins: "http://localhost:64, http://medmy, http://localhost:190", headers: "*", methods: "*")]` works fine but as soon as I add a subsite which makes it `[EnableCors(origins: "http://localhost:64, http://medmy, http://localhost:190, http://medmy/mysubsite", headers: "*", methods: "*")]` the API call doesn't work. It is a SharePoint sub site, so should it still be `http://medmy`, even though the call is coming from `http://medmy/mysubsite`? – Si8 Mar 30 '17 at 19:07
  • Don't end any of your `origin`s with a trailing `/`. This caught me out in a deployment when it was working fine locally. – wolfyuk Mar 28 '18 at 14:20
  • This method does indeed work! One key difference on this method vs. changing `web.config` method is that unless your request is truly cross-origin it wont return `access-control-allow-origin` header! – bbodenmiller Feb 04 '20 at 04:43
16

You just need to change some files. This works for me.

Global.ascx

public class WebApiApplication : System.Web.HttpApplication {
    protected void Application_Start()
    {
        WebApiConfig.Register(GlobalConfiguration.Configuration);
    } }

WebApiConfig.cs

All the requests has to call this code.

public static class WebApiConfig {
    public static void Register(HttpConfiguration config)
    {
        EnableCrossSiteRequests(config);
        AddRoutes(config);
    }

    private static void AddRoutes(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "Default",
            routeTemplate: "api/{controller}/"
        );
    }

    private static void EnableCrossSiteRequests(HttpConfiguration config)
    {
        var cors = new EnableCorsAttribute(
            origins: "*", 
            headers: "*", 
            methods: "*");
        config.EnableCors(cors);
    } }

Some Controller

Nothing to change.

Web.config

You need to add handlers in your web.config

<configuration> 
  <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> 
</configuration>
Bharath theorare
  • 524
  • 7
  • 27
Jonas Ribeiro
  • 306
  • 2
  • 3
  • 1
    finally my problem was the webconfig – Geomorillo Apr 06 '17 at 20:45
  • 1
    The config change did the trick for me.. i had : remove handlers ExtensionlessUrlHandler-ISAPI-4.0_32bit ExtensionlessUrlHandler-ISAPI-4.0_64bit ExtensionlessUrlHandler-Integrated-4.0 WebDAV and add handlers ExtensionlessUrlHandler-ISAPI-4.0_32bit ExtensionlessUrlHandler-ISAPI-4.0_64bit ExtensionlessUrlHandler-Integrated-4.0..... This caused it to not work.. the change as per this answer fixed it. thanks – biso Feb 23 '18 at 12:37
14

In case of CORS request all modern browsers respond with an OPTION verb, and then the actual request follows through. This is supposed to be used to prompt the user for confirmation in case of a CORS request. But in case of an API if you would want to skip this verification process add the following snippet to Global.asax

        protected void Application_BeginRequest(object sender, EventArgs e)
        {
            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
            if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
            {
                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "POST, PUT, DELETE");

                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
                HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
                HttpContext.Current.Response.End();
            }
        }

Here we are just by passing the check by checking for OPTIONS verb.

Neeraj
  • 596
  • 7
  • 9
  • 1
    Thank you, Isn't that http level configuration what the dependency Microsoft.AspNet.WebApi.Cors should deal with so we don't need to configure it explicitly in our code? I can have it working by doing something similar in web.config but the point is to use Microsoft.AspNet.WebApi.Cors to deal with it and have it attribute-configurable. – diegosasw Apr 01 '15 at 22:35
  • Not sure why you would want to skip the verification process entirely. At least not in production. If you want to use the built in stuff as it was designed you can configure CORS properly and then ignore routing for OPTIONS requests as detailed in my answer – BritishDeveloper Dec 22 '15 at 17:15
  • 1
    If you find IE works while Chrome and Firefox fails, this very well be the solution. It worked for me in a development environment. – Bleeped Feb 17 '16 at 16:09
  • @neeraj After trying several dozen ways, this is the only thing that made it work. Is it still an acceptable and safe method? – Connie DeCinko Jun 18 '19 at 21:09
8

I just added custom headers to the Web.config and it worked like a charm.

On configuration - system.webServer:

<httpProtocol>
  <customHeaders>
    <add name="Access-Control-Allow-Origin" value="*" />
    <add name="Access-Control-Allow-Headers" value="Content-Type" />
  </customHeaders>
</httpProtocol>

I have the front end app and the backend on the same solution. For this to work, I need to set the web services project (Backend) as the default for this to work.

I was using ReST, haven't tried with anything else.

Mathter
  • 747
  • 7
  • 14
  • As other have said, wasted time on trying to get it to work "properly" then just shoved it into the web.config – Morvael Oct 11 '17 at 13:36
  • 1
    Thank you @Mathter, this worked for me, despite enabling CORS via my startup class. Question is though, why are we having to set this in web.config instead of doing it the regular way? – Ciaran Gallagher Nov 03 '17 at 17:27
4

After some modifications in my Web.config CORS suddenly stopped working in my Web API 2 project (at least for OPTIONS request during the preflight). It seems that you need to have the section mentioned below in your Web.config or otherwise the (global) EnableCorsAttribute will not work on OPTIONS requests. Note that this is the exact same section Visual Studio will add in a new Web API 2 project.

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

I just experienced this same issue, trying to enable CORS globally. However I found out it does work, however only when the request contains a Origin header value. If you omit the origin header value, the response will not contain a Access-Control-Allow-Origin.

I used a chrome plugin called DHC to test my GET request. It allowed me to add the Origin header easily.

Rick
  • 3,361
  • 1
  • 22
  • 29
2

None of these answers really work. As others noted the Cors package will only use the Access-Control-Allow-Origin header if the request had an Origin header. But you can't generally just add an Origin header to the request because browsers may try to regulate that too.

If you want a quick and dirty way to allow cross site requests to a web api, it's really a lot easier to just write a custom filter attribute:

public class AllowCors : ActionFilterAttribute
{
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        if (actionExecutedContext == null)
        {
            throw new ArgumentNullException("actionExecutedContext");
        }
        else
        {
            actionExecutedContext.Response.Headers.Remove("Access-Control-Allow-Origin");
            actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");
        }
        base.OnActionExecuted(actionExecutedContext);
    }
}

Then just use it on your Controller action:

[AllowCors]
public IHttpActionResult Get()
{
    return Ok("value");
}

I won't vouch for the security of this in general, but it's probably a lot safer than setting the headers in the web.config since this way you can apply them only as specifically as you need them.

And of course it is simple to modify the above to allow only certain origins, methods etc.

Matthew
  • 4,149
  • 2
  • 26
  • 53
  • Don't think this works in the Visual Studio debug situation. It appears that this code is not even being reached, because Visual Studio's IIS is eating the request prior to this code, even. – Ryan Feb 19 '18 at 01:34
1

No-one of safe solution work for me so to be safer than Neeraj and easier than Matthew just add: System.Web.HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");

In your controller's method. That work for me.

public IHttpActionResult Get()
{
    System.Web.HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
    return Ok("value");
}
puipuix
  • 29
  • 1
  • I used the CORS library and put the attribute on a controller, but it did not work. Only using it globally inside WebApiConfig it did work. Using this solution ultimately worked for me per controller basis. Thanks. – tno2007 Oct 17 '19 at 01:39
0

I found this question because I was having issues with the OPTIONS request most browsers send. My app was routing the OPTIONS requests and using my IoC to construct lots of objects and some were throwing exceptions on this odd request type for various reasons.

Basically put in an ignore route for all OPTIONS requests if they are causing you problems:

var constraints = new { httpMethod = new HttpMethodConstraint(HttpMethod.Options) };
config.Routes.IgnoreRoute("OPTIONS", "{*pathInfo}", constraints);

More info: Stop Web API processing OPTIONS requests

BritishDeveloper
  • 13,219
  • 9
  • 52
  • 62
0

Hope this helps someone in the future. My problem was that I was following the same tutorial as the OP to enable global CORS. However, I also set an Action specific CORS rule in my AccountController.cs file:

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

and was getting errors about the origin cannot be null or empty string. BUT the error was happening in the Global.asax.cs file of all places. Solution is to change it to:

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

notice the * in the origins? Missing that was what was causing the error in the Global.asax.cs file.

Hope this helps someone.

jangooni
  • 167
  • 3
  • 11
0

WEBAPI2:SOLUTION. global.asax.cs:

var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);

IN solution explorer, right-click api-project. In properties window set 'Anonymous Authentication' to Enabled !!!

Hope this helps someone in the future.

hannes neukermans
  • 12,017
  • 7
  • 37
  • 56