runtime: NTLM authentication sometimes broken by multiple WWW-Authenticate headers

This issue has been split off from dotnet/runtime#17545, which turned out to be a problem in the tool being used to observe network traffic. Other users saw similar results, but under different conditions and not caused by the testing tool. That issue will be tracked here to clearly separate the two issues.

The issue tracked here occurs with the following code, targeting .NET Core 2.0:

var creds = new CredentialCache();
creds.Add(new Uri(addy),"NTLM",new NetworkCredential(Username,Password));
var handler = new HttpClientHandler
{
	Credentials = creds,
};

HttpClient client = new HttpClient(handler);
client.BaseAddress = new Uri(addy);
            
var response = await client.GetAsync("api/myresource");

In Windows, the server receives the following headers, but does not initiate the NTLM handshake:

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/8.5
WWW-Authenticate: NTLM
WWW-Authenticate: Negotiate
X-Powered-By: ASP.NET
Date: Thu, 01 Mar 2018 22:17:34 GMT
Content-Length: 1293

@dbrownxc, can you provide more information on the situation in which you were able to reproduce this issue? It would be good to have full logs for the unsuccessful authentication attempt.

cc: @seriouz @karelz @davidsh

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 21 (10 by maintainers)

Commits related to this issue

Most upvoted comments

I think I’ve tracked this issue down. Here are the conditions under which it will repro:

  • There are at least two authentication schemes enabled on the server.
  • The user only provides credentials that have an authentication scheme less secure(1) than the most secure option offered by the server.

In the conditions we see here, the user provides NTLM credentials, but the server supports both NTLM and Negotiate (which is considered more secure). WinHttpHandler erroneously chooses to attempt authentication with Negotiate. When we later detect that there are no credentials in the cache that support Negotiate, we close the connection.

This happens because the code we use to choose the authentication scheme only considers the schemes supported by the server, and not those supported by the client. If the we don’t have credentials for the most secure protocol supported by the server, we will fail the authentication attempt.

You can see the code that chooses the authentication scheme below. The parameter supportedSchemes indicates the schemes supported by the server. https://github.com/dotnet/corefx/blob/1de2b37722e0987eaea07bd8e23a3d78d4ea36b2/src/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpAuthHelper.cs#L374-L385 The fix for this is simple, and I have a tentative version working. I’m adding some additional tests now, and if all goes well I’ll try to get the PR out this afternoon.

(1) Our implementation ranks schemes in the following order: Negotiate, NTLM, Digest, Basic

Or you can use 2.1 (currently Preview2) where it is fixed.

@rmadisonhaynie For us, it is a matter of switching between NTLM and Negotiate depending on environment.

For now, what we are doing is setting this in configuration. Using the new configuration classes, we just set up a development configuration (appSettings) that works for Windows and a release configuration that works for Linux (environment variable override works here).

Yep! Thanks for getting around to sending out the repro @dbrownxc – it makes things way more manageable on our end.