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.OwinSelfHost
NuGet 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/public
endpoint is not available for anonymous requests anymore (as expected)/sample/protected
endpoint 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!