0

I have two applications and a database that I am attempting to get to all "Play nice" once deployed to IIS on separate servers. The goal is for users to authenticate with Windows initially and then still have the services communicate. We would prefer to have the users EID persist as the identity for each step/request, but if this a situation in which we HAVE to use some kind of universal AppPool account I think we are open to that.

There are no errors when I call the Odata Web API locally from IIS express from my locally launched application. The database is a MS SQL Server DB that has been running for a while, contains a TON of data used by a few apps, and returns data as expected to Application 1 when queried using Telerik Fiddler etc.

The applications and database are all running on separate servers.

The basic request scheme goes:

Client Device(User is authenticated against AD here)->GET->Application 2->GET->Application 1->SQLQUERY/DbContextRequest->SQL Server

So a client device makes a get request (from a mobile app), that request is sent to Application 2's API endpoint, who then calls on Application 1 API endpoint to retrieve the data from the database and return to Application 2, where a little bit of data organization occurs before sending that data back to the client device.

A breakdown:

Application 1: ASP.NET Web API using Odata controller to receive requests

Application 2: ASP.NET Web API using RestSharp to send requests.

Application 1 has been deployed to its server for months with no issues thus far, however this is our first attempt at calling Application 1 from another Web Service (Application 2).

Application 2 can call other Web Services hosted by our company with NO issues, it is only this one (Application 1) that returns "Unauthorized".

The trouble application (Application 1) is currently deployed to IIS. When I send a GET request to Application 1 from my local application machine running Application 2 in IIS express, the data returns as expected. Using Fiddler from my local machine also returns data from Application 1 with no issues.

However, once I deploy Application 2 to IIS, the application with the Odata controller (Application 1) returns "Unauthorized" to any request sent from Application 2 to Application 1 once deployed to IIS.

I have used log4Net and determined that whether or not I use impersonation, I am unauthorized, even when sending the request as the same user (verified by logs), or the AppPool Identity. Both my AD account and the AppPool ID account have full access to the folder of Application 1 that is returning "Unauthorized".

I am sending the request using RestSharp library:

    [HttpGet]
    [ResponseType(typeof(WorkOrderModel))]
    public IHttpActionResult GetWorkOrderDEBUG()
    {
        const string id = "1000";

        var client = new RestClient(Constants.ODATA_WEBSERVICEDOMAIN)
        {
            // Authentication needed when querying ODATA
            Authenticator = new NtlmAuthenticator()
        };
        var request = new RestRequest(Constants.ODATA_WEBSERVICE_PATH + "(" + id + ")", Method.GET);
        // add http header or request to remove odata metadata
        request.AddHeader("Accept", "application/json; odata.metadata=none");
        request.RequestFormat = DataFormat.Json;
        // Logging for debugging after deployment
        log.Debug("CURRENT IDENTITY: " + Environment.UserName);
        log.Debug("CURRENT OTHER IDENTITY: " + System.Security.Principal.WindowsIdentity.GetCurrent().Name);
        log.Debug("CURRENT MAYBE IDENTITY: " + GetUser());
        log.Debug("BaseUrl Path and Query: " + client.BaseUrl.PathAndQuery.ToString());
        log.Debug("Authenticator: " + client.Authenticator.ToString());
        log.Debug("Request Format: " + request.RequestFormat.ToString());
        log.Debug("Resource: " + request.Resource.ToString());

        // RestSharp execute request
        var response = client.Execute(request);

        // Logging for debugging after deployment
        if (response.ErrorMessage != null)
        {
            log.Debug("ErrorMessage: " + response.ErrorMessage.ToString());
        }

        if (response.ErrorException != null)
        {
            log.Debug("ErrorException.Message: " + response.ErrorException.Message.ToString());
            if (response.ErrorException.InnerException != null)
            {
                log.Debug("InnerException.Message: " + response.ErrorException.InnerException.Message.ToString());
            }
        }

        if (response.Content != null)
        {
            log.Debug("Content: " + response.Content.ToString());
        }

        log.Debug("ContentType: " + response.ContentType.ToString());
        //log.Debug("Headers: " + response.Headers.ToString());
        //log.Debug("Request: " + response.Request.ToString());
        log.Debug("ResponseStatus: " + response.ResponseStatus.ToString());
        log.Debug("ResponseURI-Path and Query: " + response.ResponseUri.PathAndQuery.ToString());
        log.Debug("ResponseURI-OriginalString: " + response.ResponseUri.OriginalString.ToString());
        log.Debug("ResponseURI-Segments: " + response.ResponseUri.Segments.ToString());
        log.Debug("Server: " + response.Server.ToString());
        log.Debug("Status Code: " + response.StatusCode.ToString());
        log.Debug("Status Description: " + response.StatusDescription.ToString());

        var content = response.Content;

        //if (content != null)
        //{
        //    log.Debug(content.ToString());
        //}

        var workOrder = JsonConvert.DeserializeObject<WorkOrderModel>(content);

        log.Debug(workOrder.ToString());

        return Ok(workOrder);
    }``

Here is my Web Config:

<system.web>
    <authentication mode="Windows" />
    <authorization>
      <allow users="*" /> 
      <deny users="?" />
    </authorization>
    <identity impersonate="true" />
    <customErrors mode="Off" />
    <compilation debug="true" targetFramework="4.6.1" />
    <httpRuntime targetFramework="4.6.1" />
    <httpModules>
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
    </httpModules>
  </system.web>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />

I think that webservice/server/controller/method paths are OK because they work fine when querying Application 1's Odata Web API Controller directly while it's running on the server/IIS using Telerik Fiddler on my local machine or SwaggerUI, so I'm not concerned there...

Here are some error logs:

    DEBUG 2019-11-15 17:00:56,786 1397487ms 
                                          Controller  GetWorkOrder       - CURRENT IDENTITY: myid123
    DEBUG 2019-11-15 17:00:56,786 1397487ms Controller  GetWorkOrder       - CURRENT OTHER IDENTITY: MBULOGIN\myid123
    DEBUG 2019-11-15 17:00:56,786 1397487ms Controller  GetWorkOrder       - BaseUrl Path and Query: /app/odata/
    DEBUG 2019-11-15 17:00:56,786 1397487ms Controller  GetWorkOrder       - Authenticator: RestSharp.Authenticators.NtlmAuthenticator
    DEBUG 2019-11-15 17:00:56,786 1397487ms Controller  GetWorkOrder       - Default Parameters:System.Collections.Generic.List`1[RestSharp.Parameter]
    DEBUG 2019-11-15 17:00:56,786 1397487ms Controller  GetWorkOrder       - Parameters: System.Collections.Generic.List`1[RestSharp.Parameter]
    DEBUG 2019-11-15 17:00:56,786 1397487ms Controller  GetWorkOrder       - Request Format: Json
    DEBUG 2019-11-15 17:00:56,786 1397487ms Controller  GetWorkOrder       - Resource: WorkOrders(100000)
    DEBUG 2019-11-15 17:00:56,802 1397503ms Controller  GetWorkOrder       - Content: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
    <title>401 - Unauthorized: Access is denied due to invalid credentials.</title>
    <style type="text/css">
    <!--
    body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}
    fieldset{padding:0 15px 10px 15px;} 
    h1{font-size:2.4em;margin:0;color:#FFF;}
    h2{font-size:1.7em;margin:0;color:#CC0000;} 
    h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;} 
    #header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;
    background-color:#555555;}
    #content{margin:0 0 0 2%;position:relative;}
    .content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}
    -->
    </style>
    </head>
    <body>
    <div id="header"><h1>Server Error</h1></div>
    <div id="content">
     <div class="content-container"><fieldset>
      <h2>401 - Unauthorized: Access is denied due to invalid credentials.</h2>
      <h3>You do not have permission to view this directory or page using the credentials that you supplied.</h3>
     </fieldset></div>
    </div>
    </body>
    </html>

    DEBUG 2019-11-15 17:00:56,802 1397503ms Controller  GetWorkOrder       - ContentType: text/html
    DEBUG 2019-11-15 17:00:56,802 1397503ms Controller  GetWorkOrder       - Headers: System.Collections.Generic.List`1[RestSharp.Parameter]
    DEBUG 2019-11-15 17:00:56,802 1397503ms Controller  GetWorkOrder       - Request: RestSharp.RestRequest
    DEBUG 2019-11-15 17:00:56,802 1397503ms Controller  GetWorkOrder       - ResponseStatus: Completed
    DEBUG 2019-11-15 17:00:56,802 1397503ms Controller  GetWorkOrder       - ResponseURI-Path and Query: /app/odata/WorkOrders(1000000)
    DEBUG 2019-11-15 17:00:56,802 1397503ms Controller  GetWorkOrder       - ResponseURI-OriginalString: https://example.com/app/odata/WorkOrders(1000000)
    DEBUG 2019-11-15 17:00:56,802 1397503ms Controller  GetWorkOrder       - ResponseURI-Segments: System.String[]
    DEBUG 2019-11-15 17:00:56,802 1397503ms Controller  GetWorkOrder       - Server: Microsoft-IIS/8.5
    DEBUG 2019-11-15 17:00:56,802 1397503ms Controller  GetWorkOrder       - Status Code: Unauthorized
    DEBUG 2019-11-15 17:00:56,802 1397503ms Controller  GetWorkOrder       - Status Description: Unauthorized

When sending the GET request without impersonation, the error log from log4net is identical to the above except the identity changes to that of the AppPool.

I don't understand what's different about the request coming from IIS to Application 1's Odata Web Api controller compared to when it's coming locally from my machine running that app in IIS express, even more so after utilizing impersonation. Once impersonated, the same account that had no issues running locally is "Unauthorized".

I've checked the Application's authorization policies in the IIS GUI, Windows Auth is allowed on both instances of IIS.

Is this a Server/IIS setting? If so, for which application? I'm really not sure what else to look into, it seems like there is some kind of information being sent along with the GET request from Application 2 when it comes from IIS compared to my local machine that triggers the request to not be authorized...but what could it be?

cklimowski
  • 589
  • 6
  • 21
  • Is Application 2's app pool identity a domain account? I assume yes, but you didn't actually specify. However, according to http://appetere.com/post/introduction-to-identity-impersonation-with-iis-and-aspnet, the `` setting means "this would allow us to make resource requests from ASP.NET while impersonating the Windows identity of whoever was logged on". Now, is the web.config snippet in your question from Application 1 or 2? If it's 2, then that means it's connecting to App 1 using the identity of the client, not its own, and that probably would be a problem. – howcheng Nov 18 '19 at 22:48
  • App 2's ID is a system account on our domain. From talking to my team further, we have determined that we want the requests from App 2 to arrive at App1 as the AppPool ID of App2. I have adjusted web/config to allow for this, set up detailed logging to verify the ID of the request, and still getting Access Denied, only when the app is running on IIS... – cklimowski Dec 04 '19 at 18:50
  • Are you using `[Authorize]` on the controller(s) or as a global filter and if so, does App2's ID either explicitly authorized or does it have the proper role to use the API? Otherwise, the only thing I can suggest is to scrutinize App1's web.config, machine.config, ACLs, etc, and compare them to those on the applications that App2 can successfully use to see if anything is different. – howcheng Dec 04 '19 at 20:54
  • There is no Authorize attribute on the controller or global filter of App 1. The team has been scrutinizing things, and we think that it has to do with the Kerberos "Double Hop" authentication issue...which I'm finding less and less info on. I'll update the question later this week. Hopefully I can find a solution. – cklimowski Dec 04 '19 at 22:08
  • 1
    Maybe this? https://stackoverflow.com/questions/14928350/how-can-i-fix-the-kerberos-double-hop-issue – howcheng Dec 04 '19 at 22:19
  • Thanks @howcheng, I'll look into it! – cklimowski Dec 05 '19 at 23:35

0 Answers0