WebAPI CORS with Windows Authentication - allow anonymous OPTIONS request

I have a RAP WebAPI 2 service running Windows Authentication. It is hosted separately from the website, so I enabled CORS using the ASP.NET CORS NuGet package. My client site uses AngularJS.

So far, here is what I have experienced:

  • I did not have the Credentials set installed, so CORS requests returned 401. Solved by adding withCredentials to my $ httpProvider configuration.
  • Then I installed my EnableCorsAttribute with a wildcard that is not allowed when using credentials. Solved by setting an explicit list of sources.
  • This allowed my GET requests to succeed, but my POST issued a pre-sale request, and I did not create any controller actions to support the OPTIONS verb. To solve this problem, I applied MessageHandler as a global OPTIONS handler. It just returns 200 for any OPTIONS request. I know this is not perfect, but it works for now in Fiddler.

Where I am stuck - my Angular pre-calls do not include credentials. According to this answer , this is by design, since OPTIONS requests are for anonymity. However, Windows Authentication stops the request using 401.

I tried putting the [AllowAnonymous] attribute in my MessageHandler. It works on my development computer - OPTIONS verbs do not require authentication, but other verbs. However, when I create and deploy on a test server, I keep getting 401 in my OPTIONS request.

Can [AllowAnonymous] be applied to my MessageHandler when using Windows Authentication? If so, any recommendations on how to do this? Or is it the wrong rabbit hole, and should I look at a different approach?

UPDATE: I was able to get it working by installing for Windows authentication and anonymous authentication on a site in IIS. This forced everyone to allow anonymity, so I added a global Authorize filter, keeping AllowAnonymous in my MessageHandler.

However, this seems like a hack ... I always realized that you need to use only one authentication method (not mixed). If anyone has a better approach, I will be grateful for that.

+14
angularjs cors asp.net-web-api windows-authentication
source share
9 answers

I used self-hosting with HttpListener and the following solution worked for me:

  • I allow anonymous OPTIONS requests
  • Enable CORS with SupportsCredentials set true
var cors = new EnableCorsAttribute("*", "*", "*"); cors.SupportsCredentials = true; config.EnableCors(cors); var listener = appBuilder.Properties["System.Net.HttpListener"] as HttpListener; if (listener != null) { listener.AuthenticationSchemeSelectorDelegate = (request) => { if (String.Compare(request.HttpMethod, "OPTIONS", true) == 0) { return AuthenticationSchemes.Anonymous; } else { return AuthenticationSchemes.IntegratedWindowsAuthentication; }}; } 
+14
source share

For some time, I struggled to make CORS queries work under the following restrictions (very similar to those that the OP had):

  • Windows Authentication for All Users
  • Anonymous authorization not allowed
  • Works with IE11, which in some cases does not send CORS preview requests (or at least does not reach global.asax BeginRequest as an OPTIONS request)

My last configuration is this:

web.config - allow unprofessional (anonymous) pre-flight requests (OPTIONS)

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

global.asax.cs - respond correctly with headers that allow the caller from another domain to receive data

 protected void Application_AuthenticateRequest(object sender, EventArgs e) { if (Context.Request.HttpMethod == "OPTIONS") { if (Context.Request.Headers["Origin"] != null) Context.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]); Context.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, MaxDataServiceVersion"); Context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); Context.Response.AddHeader("Access-Control-Allow-Credentials", "true"); Response.End(); } } 

CORS allowing

 public static class WebApiConfig { public static void Register(HttpConfiguration config) { // all requests are enabled in this example. SupportsCredentials must be here to allow authenticated requests var corsAttr = new EnableCorsAttribute("*", "*", "*") { SupportsCredentials = true }; config.EnableCors(corsAttr); } } protected void Application_Start() { GlobalConfiguration.Configure(WebApiConfig.Register); } 
+6
source share

I solved it very similarly, but with some details and focused on the oData service p>

I did not turn off anonymous authentication in IIS since I needed a POST request

And I added in Global.aspx (adding MaxDataServiceVersion to Access-Control-Allow-Headers ) the same code as above

 protected void Application_BeginRequest(object sender, EventArgs e) { if ((Context.Request.Path.Contains("api/") || Context.Request.Path.Contains("odata/")) && Context.Request.HttpMethod == "OPTIONS") { Context.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]); Context.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,MaxDataServiceVersion"); Context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); Context.Response.AddHeader("Access-Control-Allow-Credentials", "true"); Context.Response.End(); } } 

and WebAPIConfig.cs

 public static void Register(HttpConfiguration config) { // Web API configuration and services var cors = new EnableCorsAttribute("*", "*", "*"); cors.SupportsCredentials = true; config.EnableCors(cors); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } 

and call AngularJS

 $http({ method: 'POST', url: 'http://XX.XXX.XXX.XX/oData/myoDataWS.svc/entityName', withCredentials: true, headers: { 'Content-Type': 'application/json;odata=verbose', 'Accept': 'application/json;odata=light;q=1,application/json;odata=verbose;q=0.5', 'MaxDataServiceVersion': '3.0' }, data: { '@odata.type':'entityName', 'field1': 1560, 'field2': 24, 'field3': 'sjhdjshdjsd', 'field4':'wewewew', 'field5':'ewewewe', 'lastModifiedDate':'2015-10-26T11:45:00', 'field6':'1359', 'field7':'5' } }); 
+2
source share

This is a much simpler solution - a few lines of code that allow all "OPTIONS" requests to effectively impersonate an application account. You can disable "Anonymous" and configure CORS policies for common practice, but then add the following to your global.asax.cs file:

  protected void Application_AuthenticateRequest(object sender, EventArgs e) { if (Context.Request.HttpMethod == "OPTIONS" && Context.User == null) { Context.User = System.Security.Principal.WindowsPrincipal.Current; } } 
+2
source share

Dave

After playing with the CORS package, this made it work for me: [EnableCors (originins: ", headers:" ", methods:" * ", SupportsCredentials = true )]

I had to enable SupportsCredentials = true. Origin, headers and methods set to "*"

0
source share

disable anonymous authentication in IIS if you do not need it.

Add this to your global asax:

 protected void Application_BeginRequest(object sender, EventArgs e) { if ((Context.Request.Path.Contains("api/") || Context.Request.Path.Contains("odata/")) && Context.Request.HttpMethod == "OPTIONS") { Context.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]); Context.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); Context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); Context.Response.AddHeader("Access-Control-Allow-Credentials", "true"); Context.Response.End(); } } 

Make sure that when you enable cors, you also enable the use of credentials, for example:

 public static void Register(HttpConfiguration config) { // Web API configuration and services var cors = new EnableCorsAttribute("*", "*", "*"); cors.SupportsCredentials = true; config.EnableCors(cors); // Web API routes config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } 

As you can see, I enable CORS globally and using the BeginRequest hook application, I authenticate all OPTIONS requests for api (Web Api) and odata requests (if you use it).

This works great with all browsers; on the client side, remember to add xhrFiled withCredentials, as shown below.

 $.ajax({ type : method, url : apiUrl, dataType : "json", xhrFields: { withCredentials: true }, async : true, crossDomain : true, contentType : "application/json", data: data ? JSON.stringify(data) : '' })..... 

I am trying to find another solution, avoiding using a hook, but without success so far, I would use the web.config configuration to do something like the following: WARNING CONFIGURATION DOES NOT WORK BELOW!

  <system.web> <compilation debug="true" targetFramework="4.5" /> <httpRuntime targetFramework="4.5" /> <authentication mode="Windows" /> <authorization> <deny verbs="GET,PUT,POST" users="?" /> <allow verbs="OPTIONS" users="?"/> </authorization> </system.web> <location path="api"> <system.web> <authorization> <allow users="?"/> </authorization> </system.web> </location> 
0
source share

Other solutions that I found on the Internet did not work for me or seemed too hacked; in the end, I came up with a simpler and more efficient solution:

web.config:

 <system.web> ... <authentication mode="Windows" /> <authorization> <deny users="?" /> </authorization> </system.web> 

Project Properties:

  • Enable Windows Authentication
  • Disable Anonymous Authentication

CORS setup:

 [assembly: OwinStartup(typeof(Startup))] namespace MyWebsite { public class Startup { public void Configuration(IAppBuilder app) { app.UseCors(CorsOptions.AllowAll); 

This requires the Microsoft.Owin.Cors build available on NUget.

Angular initialization:

 $httpProvider.defaults.withCredentials = true; 
0
source share

This is my decision.

Global.asax *

 protected void Application_BeginRequest(object sender, EventArgs e) { if(!ListOfAuthorizedOrigins.Contains(Context.Request.Headers["Origin"])) return; if (Request.HttpMethod == "OPTIONS") { HttpContext.Current.Response.Headers.Remove("Access-Control-Allow-Origin"); HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]); HttpContext.Current.Response.StatusCode = 200; HttpContext.Current.Response.End(); } if (Request.Headers.AllKeys.Contains("Origin")) { HttpContext.Current.Response.Headers.Remove("Access-Control-Allow-Origin"); HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]); } } 
0
source share

In our situation:

  • Windows Authentication
  • Multiple Origin CORS
  • SupportCredentials set to true
  • IIS hosting

we found that the solution was elsewhere:

In Web.Config, all we had to do was add runAllManagedModulesForAllRequests = true

 <modules runAllManagedModulesForAllRequests="true"> 

We came to this decision by looking at the decision on why Application_BeginRequest did not start.

Other configurations we had:

in web.config

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

in webapiconfig

  private static string GetAllowedOrigins() { return ConfigurationManager.AppSettings["CorsOriginsKey"]; } public static void Register(HttpConfiguration config) { //set cors origins string origins = GetAllowedOrigins(); var cors = new EnableCorsAttribute(origins, "*", "*"); config.EnableCors(cors); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); } 

BTW "*" Cors origin is not compatible with Windows authentication / SupportCredentials = true

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

0
source share

All Articles