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?