-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NTLM authentication HttpClient in Core #24490
Comments
Is this on Windows or Linux? Is there any redirection involved with your REST service? Can you attach a full repro of the problem including the code for the localhost server? |
Change the target framework of the client project to net461, and it will work |
[Edit] I think this is related to a problem that was supposedly fixed involving how default credentials are transmitted. (#8434). However, you mention that this works on loopback (localhost) but doesn't work on remote servers. I think it is a problem with how .NET Core (Windows) sets the internal WINHTTP_AUTOLOGON_SECURITY settings for how/when to release default credentials to the remote server. Since you are explicitly requesting DefaultCredentials, they should be sent. We will need to investigate and look at traces to root cause. cc: @karelz |
Ok. Thanks for the update. |
Yes, ETA is currently Q2: https://github.com/dotnet/core/blob/master/roadmap.md#upcoming-ship-dates (it could change) I set the milestone based on my best assessment of the impact (compat with .NET Framework and @davidsh's notes). It is however possible we may change it later and push it out of 2.1. |
Triage: @FenrisWolfAtMiddleEarth we are confused - your repro uses localhost, but you mention it does not work on remote machines and works on localhost. Can you please clarify? |
My client in the repro uses a make-up DNS-name "remotehost". Replace that with there host-name where you run the NtlmAuthenticationHost project: private static void Main()
{
CallService("http://remotehost:9876", "api/Hello");
Console.ReadKey();
} That should reproduce the problem. I.e. it will fail. |
Thanks for clarification. |
As @karelz mentioned in the previous comment, this is by design that the handlers don't send out default credentials to non-intranet machines. This is by design. |
Closing per @Priya91's comment. @FenrisWolfAtMiddleEarth if you believe there is still some issue lurking, it would help to be crystal clear and provide isolated repro with all details in a single post to avoid further confusion. Thanks! |
We finally figured out a solution to this problem. The solution though indicates that there is a bug in .net Core. listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication; Even though I specifically set the client to use NTLM, it would not respond to a challange which took both kerberos and ntlm. My http client was created like this: var uri = new Uri("http://service-endpoint");
var credentialsCache = new CredentialCache { { uri, "NTLM", CredentialCache.DefaultNetworkCredentials } };
var handler = new HttpClientHandler { Credentials = credentialsCache };
var httpClient = new HttpClient(handler) { BaseAddress = uri, Timeout = new TimeSpan(0, 0, 10) };
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); What solved the problem was to change the host to only accept ntlm listener.AuthenticationSchemes = AuthenticationSchemes.Ntlm; That very much sounds like a bug in the .net core HttpClient |
@karelz |
I don't think the code above specifically sets client to use NTLM. It just provides NTLM credentials, that is all. @davidsh @wfurt is that correct summary? Is there a way to force client to use specific authentication scheme? |
From the description above, this is a bug in our HTTP client stack. The server can offer multiple schemes such as Negotiate (Kerberos) and NTLM. The client will pick the strongest but only if the credential can be used for that scheme. When a NetworkCredential object is used on the client, that credential is valid for all schemes (Basic, Digest, NTLM, Negotiate). But when a CredentialCache is used, that credential is only valid for the scheme assigned to it in the cache. In this issue, the server is presenting 2 possible schemes: Negotiate and NTLM. But the client is using a CredentialCache and only has a single credential with just NTLM. We did have a bug in our HTTP stack where we would first pick Negotiate because it is the strongest. But then fail to find a credential that can be used (since we're using CredentialCache) and result in no credential being used at all. Instead, we should fallback to another scheme (such as NTLM) that the server supports and find a matching credential in our cache. I thought we fixed this bug at least in WinHttpHandler. See: PR dotnet/corefx#28105 Not sure if we fixed it in all handlers. @rmkerr might have more information. Either way, this issue should be re-opened but it might be fixed in .NET Core 2.1. |
dotnet/corefx#28105. Targeting netcore2.1 should give you the fix @FenrisWolfAtMiddleEarth |
The issue @wfurt linked to is the correct one. This should be fixed in 2.1. |
Duplicate of dotnet/corefx#27672 |
I can confirm it is working with core 2.1 |
@FenrisWolfAtMiddleEarth definitely something to pursue as now that 2.1 is out, 2.0 will go out of support in 3 months (https://github.com/dotnet/core/blob/master/microsoft-support.md) |
@FenrisWolfAtMiddleEarth var handler = new HttpClientHandler();
var credential = new NetworkCredential("", "", "");
handler.AllowAutoRedirect = true;
var ccache = new CredentialCache
{
{ new Uri(baseUrl), "NTLM", CredentialCache.DefaultNetworkCredentials }
};
handler.Credentials = ccache;
HttpClient client;
client = new HttpClient(handler)
{
BaseAddress = new Uri("http://deagxcuewebt02.sickcn.net")
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpContent contentPost = new StringContent(parameterHeaddata, Encoding.UTF8, "application/json");
HttpResponseMessage response = client.PostAsync(targetUrl_HeadData, contentPost).Result;` But it doesn't work |
This code works for me in .net core 2.1. public HttpClient Create()
{
var uri = new Uri(_urlRoot);
var credentialsCache = new CredentialCache { { uri, "NTLM", CredentialCache.DefaultNetworkCredentials } };
var handler = new HttpClientHandler { Credentials = credentialsCache };
var httpClient = new HttpClient(handler) { BaseAddress = uri };
httpClient.DefaultRequestHeaders.ConnectionClose = false;
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
ServicePointManager.FindServicePoint(uri).ConnectionLeaseTimeout = 120 * 1000; // Close connection after two minutes
return httpClient;
} |
@FenrisWolfAtMiddleEarth, I'm using the above code in a Docker container targeting Linux running core 2.1, and I'm still getting a 401 error. Any suggestions? @SakulRelda, did you ever find a fix? |
Had same issue with application running in Docker (linux) requesting REST service with enabled both Kerberos and NTLM authentication. Resolved by
new CredentialCache
{
{
rootUrl, "NTLM",
new NetworkCredential("domain\name", "pwd")
}
} |
This seems to be an issue that may reflect more broadly with HttpClient. Oddly enough occurring in a WPF app targeting .NET Framework 4.6.2. It didn't seem clear to me that the scenarios enumerated here were on .net framework but only on core. However, I am observing this behavior on 4.6.2 as well. My client code: var handler = new HttpClientHandler {
UseDefaultCredentials = true
};
_http = new HttpClient( handler ); My error response was HTTP 401. Full exception:
Solution for me was to remove "Negotiate" from the list of providers in IIS app under "Authentication", "Windows Authentication". Thus, only "NTLM" exists in my list of Windows Auth providers. The only way I could get the client to work, without changing the server's config was: var handler = new HttpClientHandler {
//UseDefaultCredentials = true
// Notable here that my "user" could NOT have the domain prefxed on it.
// If I specified domain\user it would fail. Leaving out domain worked.
Credentials = new NetworkCredential("user","pass")
}; However, this defeats the purpose of using NTLM for me as I do not have the user / pass and want it to pass-through. I modified IIS provider list and simply set UseDefaultCredentials = true. I do not know if it matters on the server side, but I am hosting a .NET Core 2.1 Web API on IIS. |
@futuralogic do you have problems with .NET Core? (2.1+) |
@karelz Issue is not .NET core specific. |
@futuralogic do you mean that it reproduces only on .NET Framework, or that reproduces on both .NET Framework and .NET Core? (which versions?) |
I am still using the issue with .net core2.1 client while calling an endpoint with explicitly setting authentication scheme to "NTLM" |
@myfriendarnab It is unclear whether your issue is related to this one. Please open a new GitHub issue. And please include enough information for us to reproduce your problem. I.e. attach a solution and/or attach WireShark, Fiddler or other traces so that we can accurately diagnose the problem. |
We perform NTLM like this (this example is a snippet from a .NET Core app that uses the new HttpClientFactory- that's why HttpClient is a property here...): public class ApiClient : IApiClient
{
public ApiClient(NetworkCredential networkCredential = null)
{
// Use passed in credentials (if any), otherwise use the user's AD credentials
var httpClientHandler = new HttpClientHandler
{
Credentials = networkCredential ?? CredentialCache.DefaultNetworkCredentials
};
Client = new HttpClient(httpClientHandler)
{
BaseAddress = new Uri("https://api.example.com/api/")
};
}
public HttpClient Client { get; set; }
} It's also worth mentioning that Windows Integrated Authentication appeared to not be working in our applications (even using this example) until we realized that Chrome and Firefox require some additional configuration to enable WIA. Seriously, we spent weeks debugging all to wind up finding this little gem by accident: Finally, make sure that your Active Directory is configured properly (I think this tool is what we used to check): https://www.microsoft.com/en-us/download/details.aspx?id=39046 It's a major headache to get this all working, but well-worth the effort now! Good luck! |
I still had to use @IharYakimush 's solution in dotnet core 3. Is this because it is coded as designed and that is the solution or is there still a bug here? Without those fixes @IharYakimush highlighted, I got the app to work on my windows machine but not on my mac. Here is my SO question before I found this thread: https://stackoverflow.com/questions/59271689/using-network-creds-in-dotnet-core-app-on-a-mac-using-httpclient This is the post I am referring to: https://github.com/dotnet/corefx/issues/25988#issuecomment-412534360 |
The Mac has limitations. The Mac doesn't have an SPNEGO provider which is what HTTP Negotiate really requires. SPNEGO is the official protocol specification for what the 'Negotiate' authentication scheme means. SPNEGO will try Kerberos first. If that fails then it switches to NTLM automatically. In your case, your Mac is not part of the Kerberos environment/realm, so it has to use NTLM. Since SPNEGO is not supported on the Mac, .NET Core will use Kerberos directly as part of handling the 'Www-authenticate: Negotiate' header. But that will fail here. When it fails, .NET Core will not try another 'Www-authenticate' header even though 'Www-authenticate: NTLM' is present. The solution posted in the StackOverflow article is correct in this case. Instead of simply saying to use any authentication scheme provided by the server, the solution explicitly uses a CredentialCache object and binds the NetworkCredential object to a specific scheme, 'NTLM' in this case. And this works for this server because it is responding with two 'Www-authenticate' response headers. So, .NET Core will thus ignore the 'Www-authenticate: Negotiate' scheme and instead use the 'Www-authenticate: NTLM' scheme directly. The Mac does support raw 'NTLM' protocol as long as the right NTLM plug-in is installed. From a pure technical perspective, the NTLM protocol is actually different from SPNEGO-NTLM. The latter uses SPNEGO packets wrapping NTLM protocol. So, bottom line summary is that for Mac, this is the only solution here if your Mac is not on a real Kerberos realm with the server that you are communicating with. |
Thank you for saving my time (y). it works for me. |
Same code above does not work in .net core 3.1 anymore. It works in .net core 2.x. |
This doesn't work in .net core 3.1. |
@Anhbta please make sure to test a small standalone HelloWorld-style repro (e.g. using |
This helped me resolve my issue or at least figure out what was going on. The frustrating thing was I had a very simple project that was in .Net Framework 4.7 and I wanted to move to .Net Core 3 or .Net 5. Everything was working in Framework, but started getting 401 errors when I moved to .Net Core. I would have expected a "PlatformNotSupportedException" rather than just a 401 unauthorized error. |
I am trying to use the HttpClient to access a REST service which requires NTLM authentication. However I keep getting a 401 Unauthorized. My code looks like this
My target framework is netcoreapp2.0. If I change to net461, it will work. Not sure what I am doing wrong?
[EDIT] Add C# syntax highlighting by @karelz
The text was updated successfully, but these errors were encountered: