Ok, this is a long question, but I think it worth this. What we have:
A sample dummy C# console application, that start self-hosted owin ASP.Net WebAPI service (
Microsoft.AspNet.WebApi.OwinSelfHostNuGet package):class Program { static void Main(string[] args) { var url = "http://localhost:8080/"; Console.WriteLine($"Starting on {url}"); using (WebApp.Start<Startup>(url)) { Console.WriteLine("Success! Press any key to stop..."); Console.ReadKey(); } } }OWIN startup class:
class Startup { public void Configuration(IAppBuilder app) { // enable Windows AND Anonymous authentication var listener = app.Properties["System.Net.HttpListener"] as HttpListener; listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous | AuthenticationSchemes.IntegratedWindowsAuthentication; // configure WebAPI var config = new HttpConfiguration(); config.MapHttpAttributeRoutes(); app.UseWebApi(config); } }Sample WebAPI controller with two public methods:
[RoutePrefix("sample"), Authorize] public class SampleController : ApiController { [Route("public"), AllowAnonymous] public object GetPublicSample() { var message = $"Hi there, mr. {User?.Identity?.Name ?? "ANONYMOUS"}"; return new { sample = 0, message }; } [Route("protected")] public object GetProtectedSample() { var message = $"Hi there, mr. {User?.Identity?.Name ?? "ANONYMOUS"}"; return new { sample = 42, message }; } }
Now, when we run our project and point chrome to http://localhost:8080/sample/public this request is invoked:
GET /sample/public HTTP/1.1
Host: localhost:8080
HTTP/1.1 200 OK
Content-Length: 50
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Wed, 28 Feb 2018 08:05:56 GMT
{"sample":0,"message":"Hi there, mr. ANONYMOUS"}
But when we go to http://localhost:8080/sample/protected we have this:
GET /sample/protected HTTP/1.1
Host: localhost:8080
HTTP/1.1 401 Unauthorized
date: Wed, 28 Feb 2018 08:19:01 GMT
www-authenticate: Negotiate,NTLM
server: Microsoft-HTTPAPI/2.0
content-length: 61
content-type: application/json; charset=utf-8
{"Message":"Authorization has been denied for this request."}
And this is almost "as expected" except 1 thing. I expect that when my browser receives 401 HTTP response with www-authenticate header(s), he will try to repeate same request with specified authentication (if there where no any other Authorization header in request). But he does not, for some reason:(
To make things more intresting, we can see that AuthenticationSchemes.IntegratedWindowsAuthentication is actualy AuthenticationSchemes.Negotiate | AuthenticationSchemes.Ntlm and when I remove one of them things starts to work as expected! For example: replace IntegratedWindowsAuthentication with Negotiate in our Startup class and head your brouser to http://localhost:8080/sample/protected
GET /sample/protected HTTP/1.1
Host: localhost:8080
HTTP/1.1 200 OK
date: Wed, 28 Feb 2018 08:29:55 GMT
www-authenticate: tOkeN1231351234153=
server: Microsoft-HTTPAPI/2.0
content-length: 59
content-type: application/json; charset=utf-8
{"sample":42,"message":"Hi there, mr. DOMAIN\\username"}
Generally, our server first response with 401 HTTP status and set header www-authenticate: Negotiate and then browser repeat request with additional Authorization header. Same thing if we replace IntegratedWindowsAuthentication with Ntlm.
And one more example to make things clearer. If we remove AuthenticationSchemes.Anonymous and leave only AuthenticationSchemes.IntegratedWindowsAuthentication we will notice 2 things in result:
/sample/publicendpoint is not available for anonymous requests anymore (as expected)/sample/protectedendpoint now working as it should (!)
And if we look at first 401 server response, we will notice that there is two www-authenticate headers instead of one (as before):
GET /sample/protected HTTP/1.1
Host: localhost:8080
HTTP/1.1 401 Unauthorized
Content-Length: 0
Server: Microsoft-HTTPAPI/2.0
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
Date: Wed, 28 Feb 2018 08:44:04 GMT
So, my questions is: Is it "OK" to put multiple authentication schemes in single www-authenticate header? If "Yes, it's OK", why my chrome not nandling this situation? If "No! it's all way wrong!", why HttpListener act like this and how can I bypass this? Pls hlp!