Coder Perfect

How can I get HttpClient to include credentials in the request?

Problem

I have a web application that communicates with a Windows service (hosted on IIS). The ASP.Net MVC Web API (self-hosted) is used by the Windows service, and it may be talked with over http using JSON. The web application is set up to impersonate the user who makes the request to the web application, with the assumption that the user who makes the request to the web application should be the same user that the web application uses to make the service call. This is how the structure looks:

(In the samples below, the user highlighted in red is the one being referred to.)

Using a HttpClient, the web application sends requests to the Windows service.

var httpClient = new HttpClient(new HttpClientHandler() 
                      {
                          UseDefaultCredentials = true
                      });
httpClient.GetStringAsync("http://localhost/some/endpoint/");

This sends a request to the Windows service, but the credentials are not passed correctly (the service reports the user as IIS APPPOOL\ASP.NET 4.0). This is the exact opposite of what I want to happen.

If I replace the following code with a WebClient, the user’s credentials are successfully passed:

WebClient c = new WebClient
                   {
                       UseDefaultCredentials = true
                   };
c.DownloadStringAsync(new Uri("http://localhost/some/endpoint/"));

With the above code, the service identifies the user as the one who made the web application request.

What am I doing wrong with the HttpClient implementation that is preventing it from correctly passing the credentials (or is it a flaw with the HttpClient)?

The HttpClient appeals to me because it includes an async API that works well with Tasks, whereas the WebClient’s async API requires the use of events.

Asked by adrianbanks

Solution #1

You may set up HttpClient to pass credentials automatically in the following way:

var myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });

Answered by Sean

Solution #2

This was also a difficulty for me. Thanks to @tpeczek’s study in the following SO article, I built a synchronous solution: HttpClient is unable to authenticate to the ASP.NET Web Api service.

My approach makes use of a WebClient, which, as you properly pointed out, handles the credentials with ease. Because Windows security disables the ability to generate new threads under an impersonated account, HttpClient does not work (see SO article above.) The problem is caused by HttpClient’s use of the Task Factory to create new threads. WebClient, on the other hand, operates synchronously on the same thread, allowing it to bypass the rule and provide its credentials.

The code is functional, but it is not asynchronous.

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: Newtonsoft is a NuGet package that is required. WebAPI utilizes the same JSON serializer as Json.

Answered by Joshua

Solution #3

You’re attempting to get NTLM to forward the identity to the next server, which it cannot do – it can only perform impersonation, which provides you access to local resources. It will not allow you to pass through a machine border. When all servers and apps in the chain are appropriately configured, and Kerberos is set up correctly on the domain, Kerberos authentication provides delegation (what you require) by using tickets, and the ticket can be forwarded on. In a nutshell, you must transition from NTLM to Kerberos.

Start at http://msdn.microsoft.com/en-us/library/ff647076.aspx to learn more about the Windows Authentication options available to you and how they function.

Answered by BlackSpy

Solution #4

So, thank you to all of the aforementioned contributors. I’m running.NET 4.6, and we experienced the identical problem. I spent some time debugging System.Net.Http and the HttpClientHandler and discovered the following:

    if (ExecutionContext.IsFlowSuppressed())
    {
      IWebProxy webProxy = (IWebProxy) null;
      if (this.useProxy)
        webProxy = this.proxy ?? WebRequest.DefaultWebProxy;
      if (this.UseDefaultCredentials || this.Credentials != null || webProxy != null && webProxy.Credentials != null)
        this.SafeCaptureIdenity(state);
    }

So, after determining that ExecutionContext.IsFlowSuppressed() was the issue, I wrapped our Impersonation function with the following:

using (((WindowsIdentity)ExecutionContext.Current.Identity).Impersonate())
using (System.Threading.ExecutionContext.SuppressFlow())
{
    // HttpClient code goes here!
}

WindowsIdentity is grabbed by the code inside of SafeCaptureIdenity (not my spelling mistake). Our impersonated identity is Current(). We are currently suppressing flow, so this is being picked up. This is reset after invocation due to the using/dispose.

Phew, everything appears to be working now for us!

Answered by Chullybun

Solution #5

Using WindowsIdentity and a System.Net.Http.HttpClient with UseDefaultCredentials = true in.NET Core, I was able to pass the authenticated user’s Windows credentials to a back end service. RunImpersonated.

HttpClient client = new HttpClient(new HttpClientHandler { UseDefaultCredentials = true } );
HttpResponseMessage response = null;

if (identity is WindowsIdentity windowsIdentity)
{
    await WindowsIdentity.RunImpersonated(windowsIdentity.AccessToken, async () =>
    {
        var request = new HttpRequestMessage(HttpMethod.Get, url)
        response = await client.SendAsync(request);
    });
}

Answered by scourge192

Post is based on https://stackoverflow.com/questions/12212116/how-to-get-httpclient-to-pass-credentials-along-with-the-request