401 response for CORS request in IIS with Windows Auth enabled

51,854

Solution 1

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.

Solution 2

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

Solution 3

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!

Solution 4

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

Solution 5

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>
Share:
51,854
dariusriggins
Author by

dariusriggins

Updated on July 09, 2022

Comments

  • dariusriggins
    dariusriggins almost 2 years

    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?

  • link64
    link64 over 9 years
    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
  • Sathish Naga
    Sathish Naga over 9 years
    I'm skip checking for Is user authenticated for OPTIONS requests. I'll add code sample.
  • UserControl
    UserControl over 9 years
    Does this require anonymous auth module enabled in IIS?
  • Jan Remunda
    Jan Remunda over 9 years
    Apparently yes. "Anonymous authentication gives users access to the public areas of your Web or FTP site without prompting them for a user name or password." bit.ly/1wjLdO9
  • Lex Li
    Lex Li over 9 years
    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
    Lex Li over 9 years
    Just made a blog post, blog.lextudio.com/2014/11/… with more info on IIS 6 and classic mode users.
  • user510101
    user510101 over 9 years
    @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.
  • Lex Li
    Lex Li over 9 years
    @ThiagoSilva, I just updated the post to point out which article you should read. developer.mozilla.org/en-US/docs/Web/HTTP/… contains even sample requests/responses so that you can easily follow.
  • Stu
    Stu over 8 years
    @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!
  • Paul Lernmark
    Paul Lernmark about 8 years
    Do you see any potential security issues with this?
  • AhmadWabbi
    AhmadWabbi almost 8 years
    This does NOT WORK!
  • beewest
    beewest over 7 years
    Any example for asp.net core webapi pls?
  • Ben Cottrell
    Ben Cottrell over 7 years
    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!
  • MattEvansDev
    MattEvansDev over 7 years
    @beewest see my answer to your other post stackoverflow.com/questions/39609811/…
  • jpfreire
    jpfreire over 7 years
    I'm trying the same but without credentials. No luck even with anonymous auth on
  • Shiroy
    Shiroy about 7 years
    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.
  • Stuart
    Stuart over 6 years
    This absolutely does work, but there is a gotcha: the order is significant. You must specify the "allow" tag first and the "deny" tag second. Do it the other way around and you'll be frustrated.
  • Tobberoth
    Tobberoth over 6 years
    Seems like a neat solution. Now, how do I set up this configuration for asp.net core?
  • user2864740
    user2864740 over 5 years
    "Worked For Me" in IIS + Basic Auth, which has having the same issue :}
  • user2864740
    user2864740 over 5 years
    @AhmadWabbi Works Here (TM)
  • MadMoai
    MadMoai over 5 years
    This doesn't work in Chrome. I'm using withCredentials=true and the preflight OPTIONS request still doesn't send the credentials.
  • ysfaran
    ysfaran over 5 years
    great! I used following CORS-config in my web.config: <cors enabled="true"> <add origin="http://192.168.3.253:5001" allowCredentials="true" maxAge="120"> <allowHeaders allowAllRequestedHeaders="true" /> <allowMethods> <add method="GET" /> <add method="HEAD" /> <add method="POST" /> <add method="PUT" /> <add method="DELETE" /> </allowMethods> </add> </cors>
  • Luke
    Luke over 5 years
    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.
  • Ryan Mann
    Ryan Mann over 5 years
    @Stuart Thank You!! I would never have thought I needed to change the order, just saved my Sanity.... That fixed it.
  • Alfawan
    Alfawan over 3 years
    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!!
  • supriya khamesra
    supriya khamesra almost 3 years
    The things working for me using Global.asax till March 2021 but now it has stopped working in Chrome browser. GET Request working with CORS but POST are not working and giving 401 UnAuthorization error. Is there any other header or configuration we need to make it work. I am also using the same Headers in my WCF service which is Windows Authenticated and is being called from a page hosted in different site . Any help will be greateful