Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am struggling to understand and set up a Service and Consumer where the Service will run as the user logged into the Consumer.

My consumer is an MVC application. My Service is a Web Api application. Both run on separate servers within the same domain. Both are set to use Windows Auth.

My consumer code is:

private T GenericGet<T>(string p)
    {
        T result = default(T);

        HttpClientHandler handler = new HttpClientHandler() { PreAuthenticate = true, UseDefaultCredentials = true };
        using (HttpClient client = new HttpClient(handler))
        {
            client.BaseAddress = new Uri(serviceEndPoint);

            HttpResponseMessage response = client.GetAsync(p).Result;
            if (response.IsSuccessStatusCode)
                result = response.Content.ReadAsAsync<T>().Result;
        }

        return result;
    }

In my Service I call User.Identity.Name to get the caller ID but this always comes back as the consumer App Pool ID, not the logged in user. The consumer App Pool is running as a Network Service, the server itself is trusted for delegation. So how do I get the logged in User? Service code:

    // GET: /Modules/5/Permissions/
    [Authorize]
    public ModulePermissionsDTO Get(int ModuleID)
    {
        Module module= moduleRepository.Find(ModuleID);
        if (module== null)
            throw new HttpResponseException(HttpStatusCode.NotFound);

        // This just shows as the App Pool the MVC consumer is running as (Network Service).
        IPrincipal loggedInUser = User;

        // Do I need to do something with this instead?
        string authHeader = HttpContext.Current.Request.Headers["Authorization"];

        ModulePermissionsDTO dto = new ModulePermissionsDTO();
        // Construct object here based on User...

        return dto;
    }

According to this question, Kerberos is required to make this set up work because the HttpClient runs in a separate thread. However this confuses me because I thought the request sends an Authorization header and so the service should be able to use this and retrieve the user token. Anyway, I have done some testing with Kerberos to check that this correctly works on my domain using the demo in "Situation 5" here and this works but my two applications still wont correctly pass the logged in user across.

So what do I need to do to make this work? Is Kerberos needed or do I need to do something in my Service to unpack the Authorisation header and create a principal object from the token? All advice appreciated.

like image 721
James Avatar asked Oct 11 '13 10:10

James


1 Answers

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.

like image 86
Eiríkur Fannar Torfason Avatar answered Sep 21 '22 12:09

Eiríkur Fannar Torfason