NTLM Authentication with HTTP Client
In rare cases you will face a system which is secured by NTLM Authentication. It can even expose a REST API. In this blog post, I will show you how to easily interact with such system using a built in HttpClient. or any 3rd party Http client.
The problem
How to correctly authenticate against a RESTful service, which is secured by NTLM.
NTLM
If you never heard of it, it stands for NT (New Technology) LAN Manager (NTLM). It’s a suite of Microsoft security protocols intended to provide authentication, integrity, and confidentiality to users. It is widely deployed, even on new systems, mostly because of compatibility reasons. However even Microsoft does not recommend using it.
Correct usage of Http Client
It is not a good practice to create a new instance of HttpClient for every request you send. Mostly because an HttpClient is just a wrapper around a set of HTTP requests. The heavy lifting is done by a HttpMessageHandler. By creating a new HttpClient every time with a default constructor, you are also creating a new instance of the mentioned HttpMessageHandler, This can potentially lead to System.Net.Sockets.SocketException. The best practice is to reuse HttpMessageHandler among multiple HttpClients. Microsoft recommends using HttpClientFactory for that.
NTLM with HttpClientHandler
Including NTLM authentication in HTTP request is pretty simple. One does simply have to set a Credentials property of a HttpClientHandler.
new HttpClientHandler
{
Credentials = new NetworkCredential(options.Username, options.Password, options.Domain)
};
The solution
Now we have to integrate all these parts together.
Create a configuration class for loading credentials
public class NTLMOptions
{
public string BaseUrl { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Domain { get; set; }
}
Create strongly typed NTLM Client
public class NtlmClient
{
private readonly HttpClient _client;
public NtlmClient(HttpClient client)
{
_client = client;
}
public Task<string> Foo()
{
return _client.GetStringAsync("api/foo/bar");
}
}
Setup dependency injection
// add strongly typed options
services.AddOptions<NTLMOptions>()
.Bind(Configuration.GetSection("NTLM"));
// configure http client
services
.AddHttpClient<NtlmClient>((s, client) =>
{
var options = s.GetRequiredService<IOptions<NTLMOptions>>().Value;
client.BaseAddress = new Uri(options.BaseUrl);
})
.ConfigurePrimaryHttpMessageHandler((s) =>
{
var options = s.GetRequiredService<IOptions<NTLMOptions>>().Value;
return new HttpClientHandler
{
Credentials = new NetworkCredential(options.Username, options.Password, options.Domain)
};
});
Use your strongly typed client
public class MyService
{
private readonly NtlmClient _client;
public MyService(NtlmClient client)
{
_client = client;
}
public async Task Work()
{
var data = await _client.Foo();
// work with data
}
}
Simply just request your strongly typed client as a dependency.
Summary
- Do not create HttpClient directly, but ask for it from dependency injection instead
- Configure message handler to use NTLM authentication in dependency injection configuration
- Profit!
In order to use this approach with a non build in HttpClient, one does simply have to pass the HttpClient into the 3rd party HttpClient’s constructor, like in the example below:
public class NtlmClient
{
private readonly IClient _client;
public NtlmClient(HttpClient client, IOptions<NTLMOptions> options) =>
_client = new FluentClient(new Uri(options.Value.BaseUrl), client);
Comments
Richard
Amazingly helpful. Thanks!
Sam
Jan – Thanks for the article.
You made a statement that “However even Microsoft does not recommend using it.”, so I wanted to include a link to the Microsoft docs that support your statement.
https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd560653(v=ws.10)
Victor
Can this work with passthrough (without explicitly providing credentials)? something like curl –ntlm -u : http://foo.com
Brian Urbancic
Wow! Worked perfectly almost as-is. One caveat: if you are using docker be sure to change the dockerfile to include: RUN apt-get update && apt-get install -y –no-install-recommends apt-utils gss-ntlmssp
Dreamtree Miller
It does not work on linux … My app is trying to connect to ExchangeServer which uses NTLM, the app is using Microsoft.Exchange.WebServices library, and yet your proposal did not work. ExchangeServer does not understand that my client is trying to use NTLM. After the first request ExchangeServer sends 401 Unauthorised with two headers: WWW-Authenticate: NTLM WWW-Authenticate: Negotiate
Do you know solution? How to use NTLM on Linux?
To submit comments, go to GitHub Discussions.