49

I'm trying to enable CORS support in my WebAPI project, and if I enable Anonymous Authentication then everything works fine, but with Windows Auth + disabled anonymous authentication, the OPTIONS request sent always returns a 401 unauthorized response. The site requesting it is on the DOMAIN so should be able to make the call, is there any way to get around the issue without disabling Windows Authentication?

dariusriggins
  • 1,394
  • 1
  • 14
  • 30
  • Did you check if Integrated Windows Authentication is actually supported on your machine? [http://technet.microsoft.com/en-us/library/cc754628%28v=WS.10%29.aspx](http://technet.microsoft.com/en-us/library/cc754628%28v=WS.10%29.aspx) – Artem Oboturov May 30 '12 at 12:24
  • Yes, running on W7 Ultimate & also on Server 2008. I'll paste in a reply I got from MS, it seems possible, just not easy by any means, we are going to switch to a more oauth style instead and separate our API to allow anonymous auth, but issue tokens for authorization. – dariusriggins May 30 '12 at 16:23

13 Answers13

42

You can allow only OPTIONS verb for anonymous users.

<system.web>
  <authentication mode="Windows" />
    <authorization>
      <allow verbs="OPTIONS" users="*"/>
      <deny users="?" />
  </authorization>
</system.web>

According W3C specifications, browser excludes user credentials from CORS preflight: https://dvcs.w3.org/hg/cors/raw-file/tip/Overview.html#preflight-request

Jan Remunda
  • 7,504
  • 8
  • 49
  • 60
26

Several years later, but through the answer from @dariusriggins and @lex-li I have managed to add the following code to my Global.asax:

    public void Application_BeginRequest(object sender, EventArgs e)
    {
        string httpOrigin = Request.Params["HTTP_ORIGIN"];
        if (httpOrigin == null) httpOrigin = "*";
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", httpOrigin);
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Token");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Credentials", "true");

        if (Request.HttpMethod == "OPTIONS")
        {
            HttpContext.Current.Response.StatusCode = 200;
            var httpApplication = sender as HttpApplication;
            httpApplication.CompleteRequest();
        }
    }

the httpOrigin is actually looked up in a list of allowed hosts but that just complicated things. This means that all other requests are validated but options just returns.

Thanks for this question, I would have been lost without it!

Stu
  • 2,286
  • 2
  • 22
  • 38
  • 3
    I've tried many different ways to enable CORS with WebAPI running on IIS for an Angular 2 client, including adding it to web.config, data annotations on my controller actions, and calling 'EnableCors' in WebApiConfig.Register . This solution is the **only one** which actually worked with Windows Authentication (NTLM), alongside making sure the Angular 2 http client was sending `withCredentials` in the HTTP header. Thank you! – Ben Cottrell Dec 05 '16 at 13:51
  • This is brilliant!! Works like a charm! You don't even have to put it in Application_BeginRequest. You can even put it in your page load if it's just a single page you want to allow. – Shiroy Apr 07 '17 at 05:19
  • Still awesome in 2020! After being utterly frustrated trying everything else to get Windows Authentication to work. This is the one thing that actually worked. Thank you!! – Alfawan Aug 23 '20 at 16:17
15

From MS:

If you disable anonymous authentication, it’s by design that IIS would return a 401 to any request. If they have enabled Windows auth, the 401 response in that case would have a WWW-Authenticate header to allow the client to start an authentication handshake. The question then becomes whether the client that the customer is using can do Windows authentication or not.

Finally, it seems like there might be an underlying question about whether it’s possible or not to configure a URL such that anonymous access is allowed for one verb (OPTIONS, in this case), but require Windows authentication for other verbs. IIS does not support this through simple configuration. It might be possible to get this behavior by enabling both Anonymous and Windows authentication, setting ACLs on the content that deny access to the anonymous user, and then configuring the handler mapping for the URL in question so that it does not verify the existence of the file associated with the URL. But it would take some playing with it to confirm this.

dariusriggins
  • 1,394
  • 1
  • 14
  • 30
  • 3
    Just noticed that many guys have experienced the 401 errors when their Web API is protected by Windows authentication or such. CORS preflight requests do not contain credentials, so IIS will respond with 401.2 even before ASP.NET touches them. A dirty workaround is to write a HTTP module and hook to IIS pipeline, which registers on `HttpApplication.BeginRequest` event where this module returns the expected 200 response for preflight requests. This workaround only applies to IIS 7+ integrated mode. Sadly Microsoft support might not be aware of this tip. – Lex Li Jan 11 '15 at 12:17
  • 1
    Just made a blog post, https://blog.lextudio.com/2014/11/how-to-handle-cors-preflight-requests-in-asp-net-mvcweb-api-with-windows-authentication/ with more info on IIS 6 and classic mode users. – Lex Li Jan 11 '15 at 12:44
  • @LexLi read your blog, but unfortunately, you don't detail the exact implementation of the BeginRequest event, so I'm not clear on what all to include in the 200 response (e.g. headers, etc.). I understand how to build HttpModules, just would like clarification on what to respond to the preflight request. – Thiago Silva Jan 27 '15 at 20:31
  • 1
    @ThiagoSilva, I just updated the post to point out which article you should read. https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests contains even sample requests/responses so that you can easily follow. – Lex Li Jan 28 '15 at 10:24
  • @lex-li: I just posted an answer which uses just the global.asax which I wouldn't have gotten close to without your blog article. Thanks! – Stu Sep 02 '15 at 10:57
  • Any example for asp.net core webapi pls? – beewest Sep 22 '16 at 00:59
  • @beewest see my answer to your other post http://stackoverflow.com/questions/39609811/angular2-asp-net-core-service-throws-no-access-control-allow-origin-when-wind/41233517#41233517 – MattEvansDev Dec 20 '16 at 01:18
5

The easiest way to fix this is to create a rewrite rule with the condition request_method = ^OPTIONS$. Then set the action to be a custom response, set that to 200 OK. Then all options requests will respond with 200 instead of 401. This will fix the CORS issue.

Of course you still need to make sure you have the correct cross origin request headers.

This will stop options requests (which dont have any credentials) responding with 401 when integrated auth is enabled.

Luke Basil
  • 51
  • 1
  • 1
4

The accepted answer is correct however I was troubleshooting a rest api with a "node with iisnode and npm cors module" setup for a while and was not comfortable with just enabling anonymous authentication for all users. Since its a node application the system.web tag does not do much. I ended up with the following addition to the web.config:

<system.webServer>
<security>
  <requestFiltering>
    <hiddenSegments>
      <add segment="node_modules" />
    </hiddenSegments>
  </requestFiltering>
  <authorization>
    <add accessType="Allow" verbs="OPTIONS" users="?" />
    <add accessType="Deny" verbs="GET, PUT, POST, DELETE" users="?" />
  </authorization>
</security>
</system.webServer>
mrplatina
  • 189
  • 1
  • 6
  • "Worked For Me" in IIS + Basic Auth, which has having the same issue :} – user2864740 Aug 06 '18 at 21:01
  • This solution worked for us in IIS 10 Win 2016. Make sure that the URL Authorization role/feature is installed as a part of IIS Security! Otherwise this rule will not be implemented. – Luke Dec 11 '18 at 11:25
4

Related Question: IIS hijacks CORS Preflight OPTIONS request

Merging info from answers found in multiple places. If you need to enable CORS on a ASP.net page method with Windows Authentication on the intranet, this is what seems to work. Without the changes to web.config, this doesn't work.

You need to add this to Global.asax

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        string httpOrigin = HttpContext.Current.Request.Params["HTTP_ORIGIN"] ?? HttpContext.Current.Request.Params["ORIGIN"] ?? "*";
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", httpOrigin);
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Token");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Credentials", "true");

        if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
        {
            HttpContext.Current.Response.StatusCode = 200;
            var httpApplication = sender as HttpApplication;
            httpApplication.CompleteRequest();
        }
    }

And this to 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>
Shiroy
  • 1,191
  • 1
  • 11
  • 19
3

I've run into same issue today due to bug in IE 10 and 11, I'm using ServiceStack instead of WebApi, but the approach can work for you as well.

  1. Enabled Windows Integrated and Anonymous Authentication on IIS Web Site.
  2. Have a series of filters on the ServiceStack Pipeline,
    • For handling Cors and OPTIONS request, On Options request, I add necessary headers and end the request,
    • Filter for checking includng HttpRequest is Authenticated?,
    • etc filter,

After passing through all the filters, it executes the service.

CorsFeature.cs

AuthenticateFilter

In my AppHost,

appHost.Plugins.Add(new CorsFeature());

appHost.RequestFilters.Add(AuthenticateFilter.Authenticate);

I have modified the CorsFeature to handle OptionsRequest's in addition to adding headers, Authenticate Filter to check for requests authenticated!

Cyril Gandon
  • 15,798
  • 12
  • 69
  • 116
Sathish Naga
  • 1,358
  • 2
  • 10
  • 18
  • Hi, can you give a little bit more detail re what you did? I am facing similar problems and am also using ServiceStack that is deployed via SharePoint 2013 – link64 Sep 16 '14 at 05:32
  • 1
    I'm skip checking for Is user authenticated for OPTIONS requests. I'll add code sample. – Sathish Naga Sep 16 '14 at 18:10
3

Extending the answer provided by @dariusriggins. Check this post: Microsoft | Developer: Putting it all together – CORS tutorial

For IIS Configurations:

Authorization rule

Authorization rule

Authorization stage (or Authorization event), we need to make sure we only allow the anonymous requests from CORS preflight and require all other incoming requests have authentication credentials supplied. We can achieve this through Authorization Rules. A default authorization rule granting all users access to the site is already in place and supplied by default by IIS. We will start by modifying this rule to only allow anonymous users, if they send requests that are using the OPTIONS http verb. Below is the target configuration in IIS for this authorization rule:

Edit Authorization rule

Edit Authorization rule

ejderuby
  • 718
  • 5
  • 20
SubodhW
  • 69
  • 4
2

What worked for me (when working with AngularJS or JQuery) is to add withCredentials:true to each request on client:

$http.get("http://localhost:88/api/tests", {withCredentials :true})

And enabling CORS on server, this was done with Microsoft.Owin.Cors from nuget and adding it in Startup like below:

public void Configuration(IAppBuilder app)
    {
        HttpConfiguration config = new HttpConfiguration();

        ConfigureOAuth(app);

        WebApiConfig.Register(config);
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
        app.UseWebApi(config);

    }

References:

1

I'm using Web API and OWIN and I tried every suggested solution but the only thing that worked was the following

//use it in your startup class
app.Use((context, next) =>
{
    if (context.Request.Headers.Any(k => k.Key.Contains("Origin")) && context.Request.Method == "OPTIONS")
    {
        context.Response.StatusCode = 200;
        context.Response.Headers.Add("Access-Control-Allow-Origin", new string[1] { "ALLOWED_ORIGIN" });
        context.Response.Headers.Add("Access-Control-Allow-Headers", new string[4] { "Origin", "X-Requested-With", "Content-Type", "Accept" });
        context.Response.Headers.Add("Access-Control-Allow-Methods", new string[5] { "GET", "POST", "PUT", "DELETE", "OPTIONS" });
        context.Response.Headers.Add("Access-Control-Allow-Credentials", new string[1] { "true" });

        return context.Response.WriteAsync("");
    }

    return next.Invoke();
});

//this is important! Without it, it didn't work (probably because the middleware was too late)
app.UseStageMarker(PipelineStage.Authenticate);

you need to insert this code somewhere in one of your OWIN startup classes. It's important to call app.UseStageMarker(PipelineStage.Authenticate) because otherwise the preflight check failed. Further infos for UseStageMarker -> https://docs.microsoft.com/en-us/aspnet/aspnet/overview/owin-and-katana/owin-middleware-in-the-iis-integrated-pipeline

It's also important that you need to explicitly define the allowed headers. It will fail if you use * as a placeholder.

Maybe it helps somebody.

Arikael
  • 1,759
  • 1
  • 15
  • 45
1

I understand this is an old question with several possible solutions (as well as more questions), but in case anyone else comes across this, IIS CORS 1.0 is available as of Nov '17:

https://blogs.iis.net/iisteam/introducing-iis-cors-1-0

https://docs.microsoft.com/en-us/iis/extensions/cors-module/cors-module-configuration-reference

You can download it through IIS Windows Platform Installer (WPI). This should resolve many of your CORS authentication issues. Enjoy!

0

This IIS extension (IIS CORS Module) helped me to solve the 401-Unauthorized preflight request to an IIS-hosted app with Windows Authentication enabled. After installing this module I did IISRESET and in the Web.config file of my web-application I added the following:

<configuration>
    <configSections>
        <!-- ... (configSections must be the first element!) -->
    </configSections>
    <system.webServer>
        <cors enabled="true">
            <add origin="http://localhost:3000" allowCredentials="true" maxAge="120">
                <allowHeaders allowAllRequestedHeaders="true"/>
                <!-- Added 'allowMethods' just in case. -->
                <allowMethods>
                    <add method="HEAD"/>
                    <add method="GET"/>
                    <add method="POST"/>
                    <add method="PUT"/>
                    <add method="DELETE"/>
                    <add method="OPTIONS"/>
                </allowMethods>
            </add>
        </cors>
    </system.webServer>
</configuration>

Here you can find more information on how to configure the IIS CORS Module: Getting started with the IIS CORS Module.

Danik-A
  • 11
  • 3
-1

Enabling SupportCredentials on EnableCorsAttribute in WebApiConfig.cs did the trick for me:

public static void Register(HttpConfiguration config)
{        
    //enable cors request just from localhost:15136 
    var cors = new EnableCorsAttribute("http://localhost:15136", "*", "*");
    cors.SupportsCredentials = true;
    config.EnableCors(cors);

    //other stuff
}

https://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api

Make sure you send credentials when calling from javascript ({withCredentials :true})

Miroslav Adamec
  • 852
  • 10
  • 19
  • I'm trying the same but without credentials. No luck even with anonymous auth on – jpfreire Jan 12 '17 at 14:19
  • This doesn't work in Chrome. I'm using withCredentials=true and the preflight OPTIONS request still doesn't send the credentials. – MadMoai Nov 13 '18 at 21:31