WebAPI CORS with Windows Authentication - allow An

2020-01-30 07:58发布

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

So far, here's what I've been through:

  1. I didn't have withCredentials set, so the CORS requests were returning a 401. Resolved by adding withCredentials to my $httpProvider config.
  2. Next, I had set my EnableCorsAttribute with a wildcard origin, which isn't allowed when using credentials. Resolved by setting the explicit list of origins.
  3. This enabled my GET requests to succeed, but my POST issued a preflight request, and I hadn't created any controller actions to support the OPTIONS verb. To resolve this, I've implemented a MessageHandler as a global OPTIONS handler. It simply returns 200 for any OPTIONS request. I know this isn't perfect, but works for now, in Fiddler.

Where I'm stuck - my Angular preflight calls aren't including the credentials. According to this answer, this is by design, as OPTIONS requests are designed to be anonymous. However, the Windows Authentication is stopping the request with a 401.

I've tried putting the [AllowAnonymous] attribute on my MessageHandler. On my dev computer, it works - OPTIONS verbs do not require authentication, but other verbs do. When I build and deploy to the test server, though, I am continuing to get a 401 on my OPTIONS request.

Is it possible to apply [AllowAnonymous] on my MessageHandler when using Windows Authentication? If so, any guidance on how to do so? Or is this the wrong rabbit hole, and I should be looking at a different approach?

UPDATE: I was able to get it to work by setting both Windows Authentication and Anonymous Authentication on the site in IIS. This caused everything to allow anonymous, so I've added a global filter of Authorize, while retaining the AllowAnonymous on my MessageHandler.

However, this feels like a hack...I've always understood that only one authentication method should be used (no mixed). If anyone has a better approach, I'd appreciate hearing about it.

9条回答
一夜七次
2楼-- · 2020-01-30 08:39

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

  1. I allow anonymous OPTIONS requests
  2. 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;
    }};
}
查看更多
Deceive 欺骗
3楼-- · 2020-01-30 08:40

I solved it in a very similar way but with some details and focused on oData service

I didn't disable anonymous authentication in IIS since i needed it to POST request

And I've added in Global.aspx (Adding MaxDataServiceVersion in Access-Control-Allow-Headers) the same code than 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 AngularJS call

$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'
        }
    });
查看更多
女痞
4楼-- · 2020-01-30 08:40

disable anonymous authentication in IIS if you don't need it.

Than add this in 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 where you enable cors you also enable the credential usage, like:

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 application BeginRequest hook I authenticate all the OPTIONS requests for the api (Web Api) and the odata requests (if you use it).

This works fine with all browsers, in the client side remember to add the xhrFiled withCredentials like 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'm trying to find another solution avoiding to use the hook but without success until now, I would use the web.config configuration to do something like the following: WARNING THE CONFIGURATION BELOW DOESN'T WORK!

  <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>
查看更多
登录 后发表回答