How to Get Httpclient to Pass Credentials Along With the Request

How to get HttpClient to pass credentials along with the request?

I was also having this same problem. I developed a synchronous solution thanks to the research done by @tpeczek in the following SO article: Unable to authenticate to ASP.NET Web Api service with HttpClient

My solution uses a WebClient, which as you correctly noted passes the credentials without issue. The reason HttpClient doesn't work is because of Windows security disabling the ability to create new threads under an impersonated account (see SO article above.) HttpClient creates new threads via the Task Factory thus causing the error. WebClient on the other hand, runs synchronously on the same thread thereby bypassing the rule and forwarding its credentials.

Although the code works, the downside is that it will not work async.

var wi = (System.Security.Principal.WindowsIdentity)HttpContext.Current.User.Identity;

var wic = wi.Impersonate();
try
{
var data = JsonConvert.SerializeObject(new
{
Property1 = 1,
Property2 = "blah"
});

using (var client = new WebClient { UseDefaultCredentials = true })
{
client.Headers.Add(HttpRequestHeader.ContentType, "application/json; charset=utf-8");
client.UploadData("http://url/api/controller", "POST", Encoding.UTF8.GetBytes(data));
}
}
catch (Exception exc)
{
// handle exception
}
finally
{
wic.Undo();
}

Note: Requires NuGet package: Newtonsoft.Json, which is the same JSON serializer WebAPI uses.

Passing Credentials works for WebRequest but not for HttpClient

As noted here and here this behavior of HttpClient could be because of how HttpClientHandler is implemented.

"[..] the StartRequest method is executed in new thread with the credentials of the asp.net process (not the credentials of the impersonated user) [..]"

You might be seeing the difference in behavior of HttpClient and WebClient because

"HttpClient creates new threads
via the Task Factory. WebClient on the other hand, runs synchronously
on the same thread thereby forwarding its
credentials (i.e. the credentials of the impersonated user) ."


HttpClient & Windows Auth: Pass logged in User of Consumer to Service

The key is to let your MVC application (consumer) impersonate the calling user and then issue the HTTP requests synchronously (i.e. without spawning a new thread). You should not have to concern yourself with low-level implementation details, such as NTLM vs Kerberos.

Consumer

Configure your MVC application like so:

  1. Start IIS Manager
  2. Select your MVC web application
  3. Double click on 'Authentication'
  4. Enable 'ASP.NET Impersonation'
  5. Enable 'Windows Authentication'
  6. Disable other forms of authentication (unless perhaps Digest if you need it)
  7. Open the Web.config file in the root of your MVC application and ensure that <authentication mode="Windows" />

To issue the HTTP request, I recommend you use the excellent RestSharp library. Example:

var client = new RestClient("<your base url here>");
client.Authenticator = new NtlmAuthenticator();
var request = new RestRequest("Modules/5/Permissions", Method.GET);
var response = client.Execute<ModulePermissionsDTO>(request);

Service

Configure your Web API service like so:

  1. Start IIS Manager
  2. Select your Web API service
  3. Double click on 'Authentication'
  4. Disable 'ASP.NET Impersonation'.
  5. Enable 'Windows Authentication'
  6. If only a subset of your Web API methods requires users to be authenticated, leave 'Anonymous Authentication' enabled.
  7. Open the Web.config file in the root of your Web API service and ensure that <authentication mode="Windows" />

I can see that you've already decorated your method with a [Authorize] attribute which should trigger an authentication challenge (HTTP 401) when the method is accessed. Now you should be able to access the identity of your end user through the User.Identity property of your ApiController class.

.NET HttpClient do not persist authentication between reqeusts to IIS when using NTLM Negotiate

Firstly I tried to save the Authorization header for re-use it with every new request.

using System.Net.Http.Headers;

Requester requester = new Requester();
await requester.MakeRequest("http://localhost/test.txt");
await Task.Delay(100);
await requester.MakeRequest("http://localhost/test.txt");

class Requester
{
private readonly HttpClientHandler _httpClientHandler;
private readonly HttpClient _httpClient;
private AuthenticationHeaderValue _auth = null;

public Requester()
{
_httpClientHandler = new HttpClientHandler();
_httpClientHandler.UseDefaultCredentials = true;
_httpClient = new HttpClient(_httpClientHandler);
_httpClient.DefaultRequestHeaders.Add("User-Agent", Guid.NewGuid().ToString("D"));
}

public async Task<string> MakeRequest(string url)
{
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, url);
message.Headers.Authorization = _auth;

HttpResponseMessage resp = await _httpClient.SendAsync(message);
_auth = resp.RequestMessage?.Headers?.Authorization;
resp.EnsureSuccessStatusCode();
string responseText = await resp.Content.ReadAsStringAsync();
return responseText;
}
}

But it didn't work. Every time there was http code 401 asking for authentication despite of Authorization header.

The IIS logs is listed below.

2021-12-23 15:07:47 ::1 GET /test.txt - 80 - ::1 c75eeab7-a0ea-4ebd-91a8-21f5cd59c10f - 401 2 5 127
2021-12-23 15:07:47 ::1 GET /test.txt - 80 MicrosoftAccount\account@domain.com ::1 c75eeab7-a0ea-4ebd-91a8-21f5cd59c10f - 200 0 0 4
2021-12-23 15:07:47 ::1 GET /test.txt - 80 - ::1 c75eeab7-a0ea-4ebd-91a8-21f5cd59c10f - 401 1 2148074248 0
2021-12-23 15:07:47 ::1 GET /test.txt - 80 MicrosoftAccount\account@domain.com ::1 c75eeab7-a0ea-4ebd-91a8-21f5cd59c10f - 200 0 0 0

IIS's Failed Requests Tracing reports the following when receiving re-used Authentication Header:



Leave a reply



Submit