41

I have an ASP.NET Web API service that runs on a web server with Windows Authentication enabled.

I have a client site built on MVC4 that runs in a different site on the same web server that uses the HttpClient to pull data from the service. This client site runs with identity impersonation enabled and also uses windows authentication.

The web server is Windows Server 2008 R2 with IIS 7.5.

The challenge I am having is getting the HttpClient to pass the current windows user as part of its authentication process. I have configured the HttpClient in this manner:

var clientHandler = new HttpClientHandler();
clientHandler.UseDefaultCredentials = true;
clientHandler.PreAuthenticate = true;
clientHandler.ClientCertificateOptions = ClientCertificateOption.Automatic;
var httpClient = new HttpClient(clientHandler);

My understanding is that running the site with identity impersonation enabled and then building the client in this manner should result in the client authenticating to the service using the impersonated identity of the currently logged in user.

This is not happening. In fact, the client doesn't seem to be authenticating at all.

The service is configured to use windows authentication and this seems to work perfectly. I can go to http://server/api/shippers in my web browser and be prompted for windows authentication, once entered I receive the data requested.

In the IIS logs I see the API requests being received with no authentication and receiving a 401 challenge response.

Documentation on this one seems to be sparse.

I need some insight into what could be wrong or another way to use windows authentication with this application.

Thank You, Craig

Craig Anderson
  • 421
  • 1
  • 5
  • 5

4 Answers4

34

I have investigated the source code of HttpClientHandler (the latest version I was able to get my hands on) and this is what can be found in SendAsync method:

// BeginGetResponse/BeginGetRequestStream have a lot of setup work to do before becoming async
// (proxy, dns, connection pooling, etc).  Run these on a separate thread.
// Do not provide a cancellation token; if this helper task could be canceled before starting then 
// nobody would complete the tcs.
Task.Factory.StartNew(startRequest, state);

Now if you check within your code the value of SecurityContext.IsWindowsIdentityFlowSuppressed() you will most probably get true. In result the StartRequest method is executed in new thread with the credentials of the asp.net process (not the credentials of the impersonated user).

There are two possible ways out of this. If you have access to yours server aspnet_config.config, you should set following settings (setting those in web.config seems to have no effect):

<legacyImpersonationPolicy enabled="false"/>
<alwaysFlowImpersonationPolicy enabled="true"/>

If you can't change the aspnet_config.config you will have to create your own HttpClientHandler to support this scenario.

UPDATE REGARDING THE USAGE OF FQDN

The issue you have hit here is a feature in Windows that is designed to protect against "reflection attacks". To work around this you need to whitelist the domain you are trying to access on the machine that is trying to access the server. Follow below steps:

  1. Go to Start --> Run --> regedit
  2. Locate HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0 registry key.
  3. Right-click on it, choose New and then Multi-String Value.
  4. Type BackConnectionHostNames (ENTER).
  5. Right-click just created value and choose Modify.
  6. Put the host name(s) for the site(s) that are on the local computer in the value box and click OK (each host name/FQDN needs to be on it's own line, no wildcards, the name must be exact match).
  7. Save everything and restart the machine

You can read full KB article regarding the issue here.

tpeczek
  • 23,867
  • 3
  • 74
  • 77
  • I went ahead and set these values with no change in behavior. The odd thing is that I can see the client hitting the server and getting a 401 response. The HttpClient just seems to NOT be sending any authentication information and just receives the 401 and quits. Any additional thoughts? – Craig Anderson Apr 25 '12 at 19:43
  • Additionally, I changed the identity of the application pool the client is running in to be a windows account that has access to the service. This results in no change. I am lost on this one? – Craig Anderson Apr 25 '12 at 19:45
  • @CraigAnderson Can you check the values of System.Security.Principal.WindowsIdentity.GetCurrent() and SecurityContext.IsWindowsIdentityFlowSuppressed() just before you make the request with HttpClient? If it comes to the request flow it is correct. Setting PreAuthenticate to true is making the HttpClient send an Authorization header with the initial request. If the response is 401 it doesn't make any further attempts to authentice as the credentials which can be send has already been rejected. – tpeczek Apr 25 '12 at 20:16
  • @tpeczek I turned on additional logging in the application. Right before the client sends the request I can see that the current windows identity is in fact the logged in user and the value of IsWindowsIndentityFlowSuppressed is set to false. This leads to to believe that the httpclient is passing authentication to the service? – Craig Anderson Apr 25 '12 at 20:51
  • Well, I feel like we have confirmed that the client is sending the request as the user logged in. The service is choking before it even executes the first line of code. It's as if the service doesn't see or understand the windows authentication being passed to it. I am not sure how to see what is going on here to get a better idea. It's simply denying access, probably because it cannot match the windows credentials. Is this a certificate issue or something similar? – Craig Anderson Apr 25 '12 at 21:29
  • @CraigAnderson Yes it looks that HttpClient should be able to send correct credentials. In that case we need to check exact headers that are being exchanged between the client and the server. Please try Fiddler or some similar tool for request debugging. – tpeczek Apr 25 '12 at 22:08
  • @CraigAnderson One more think - are you sure you are not getting any exception from HttpClient and it just fails silently? – tpeczek Apr 25 '12 at 22:56
  • We found that the two settings above were indeed part of the solution. The other thing we found was that the system works fine if we connect from the client to the service on http://localhost/api. It does not work using a FQDN. We have the system working now but it's not truly working until we can figure out why the FQDN does not work. tpeczek, any thoughts? – Craig Anderson Apr 26 '12 at 20:35
  • @CraigAnderson I have just updated the response in the subject of access by FQDN. – tpeczek Apr 27 '12 at 06:57
  • @CraigAnderson Did it solve your issue? You haven't accepted/upvoted the answer so I have no idea if you need further assitance. – tpeczek Apr 30 '12 at 06:45
  • 1
    I am facing the same issue and the suggestion to update the "legacyImpersonationPolicy" and "alwaysFlowImpersonationPolicy" tags work but I'd like to avoid this since it's a machine wide change. This link indicates that it's possible to have a aspnet.config file per app pool but I haven't been able to get it to work. Has anyone had any luck with this? http://weblogs.asp.net/owscott/archive/2011/12/01/setting-an-aspnet-config-file-per-application-pool.aspx – Abhijeet Patel Nov 22 '13 at 05:05
  • Do you have an example or good resource to create your own HttpClientHandler to handle this situation as you recommended? Thank you. – eaglei22 Jul 06 '17 at 12:46
  • The link to that article is dead now. Perhaps https://support.microsoft.com/en-us/help/926642/ instead? – Caltor Jun 11 '20 at 13:22
10

I was also having this same problem. Thanks to the research done by @tpeczek, I developed the following solution: instead of using the HttpClient (which creates threads and sends requests async,) I used the WebClient class which issues requests on the same thread. Doing so enables me to pass on the user's identity to WebAPI from another ASP.NET application.

The obvious downside is that this will not work async.

var wi = (WindowsIdentity)HttpContext.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.

Joshua
  • 4,099
  • 25
  • 37
0

The reason why this is not working is because you need double hop authentication.

The first hop is the web server, getting impersonation with Windows authentication to work there is no problem. But when using HttpClient or WebClient to authenticate you to another server, the web server needs to run on an account that has permission to do the necessary delegation.

See the following for more details:
http://blogs.technet.com/b/askds/archive/2008/06/13/understanding-kerberos-double-hop.aspx

Fix using the "setspn" command:
http://www.phishthis.com/2009/10/24/how-to-configure-ad-sql-and-iis-for-two-hop-kerberos-authentication-2/ (You will need sufficient access rights to perform these operations.)

Just consider what would happen if any server was allowed to forward your credentials as it pleases... To avoid this security issue, the domain controller needs to know which accounts are allowed to perform the delegation.

Luis Cantero
  • 1,278
  • 13
  • 11
0

To impersonate the original (authenticated) user, use the following configuration in the Web.config file:

<authentication mode="Windows" />
<identity impersonate="true" />

With this configuration, ASP.NET always impersonates the authenticated user, and all resource access is performed using the authenticated user's security context.

Hardik Patel
  • 3,868
  • 1
  • 23
  • 37