Quite the mystery here. I have an ASP.NET MVC 4 web application using Windows Authentication that has been maintained for over 18 months without issue. Recently, it was deployed to a fresh site and I've encountered the following, very strange behavior.
I am using a jQuery 1.8.2 $.ajax call to POST data to the server endpoints to update data. This works just fine except on one page, where the AJAX POST triggers a new NTLM Negotiation. The same problem is exhibited in Chrome, IE, and Firefox. While the issue is the same in all the browsers, it manifests itself in slightly different ways:
Firefox : Received a 401 Challenge response from the server and brings up a username/password dialog asking for credential in an infinite loop. Canceling the credential checks causes the request to fail with an Unauthorized response.
IE : No response from the server and the request status shows as "(Aborted)" in the Network monitor
Chrome : No response from the server and the request status shows a "(failure)" in the Network monitor.
The core issue seems to be that the Connection: keep-alive header is not being sent with the problematic AJAX request, but is in the other cases. However, the underlying JavaScript code is nearly identical, and the AJAX calls function properly in a development environment that is also set up to use Windows authentication.
Also, attempting the set the Connection request header in the beforeSend callback has no effect.
Any insights into the root of the problem, or ways to isolate whatever difference exists between the two AJAX POSTs are most appreciated.
Working Code and Request Headers
$.ajax({
url: url,
type: "POST",
data: $("#myForm").serialize(),
cache: false,
success: function (response) {
}
});
Accept:*/*
Accept-Encoding:gzip, deflate
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:621
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
Host:www.xxx.yyy.zzz
Origin:http://www.xxx.yyy.zzz
Referer:http://www.xxx.yyy.zzz/app/resource/path
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36
X-Requested-With:XMLHttpRequest
Failing Code and Request Headers
$.ajax({
url: url,
type: "POST",
data: data,
cache: false,
success: function (data, status, xhr) {
}
});
WARN: Provisional headers are shown
Accept:*/*
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
Origin:http://www.xxx.yyy.zzz
Referer:http://www.xxx.yyy.zzz/app/resource/item/1
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36
X-Requested-With:XMLHttpRequest
I have also looked at the network process in the Chrome chrome://net-internals/#events viewer. Here is the event log from the failed request at the point it deviates from the successful one. Where the failed request gets an "HTTP/1.1 401 Unauthorized" the successful request gets an "HTTP/1.1 200 OK" response, presumably due to the presence of the Connection: keep-alive header.
2303: URL_REQUEST
Start Time: 2015-04-28 13:53:41.788
t=14736 [st= 0] +REQUEST_ALIVE [dt=71]
t=14736 [st= 0] URL_REQUEST_DELEGATE [dt=0]
t=14736 [st= 0] +URL_REQUEST_START_JOB [dt=70]
--> load_flags = 2688000 (BYPASS_DATA_REDUCTION_PROXY | MAYBE_USER_GESTURE | REPORT_RAW_HEADERS | VERIFY_EV_CERT)
--> method = "POST"
--> priority = "LOW"
--> upload_id = "0"
--> url = "http://..."
t=14736 [st= 0] URL_REQUEST_DELEGATE [dt=0]
t=14736 [st= 0] HTTP_CACHE_GET_BACKEND [dt=0]
t=14736 [st= 0] URL_REQUEST_DELEGATE [dt=0]
t=14736 [st= 0] +HTTP_STREAM_REQUEST [dt=0]
t=14736 [st= 0] HTTP_STREAM_REQUEST_BOUND_TO_JOB
--> source_dependency = 2305 (HTTP_STREAM_JOB)
t=14736 [st= 0] -HTTP_STREAM_REQUEST
t=14736 [st= 0] +HTTP_TRANSACTION_SEND_REQUEST [dt=0]
t=14736 [st= 0] HTTP_TRANSACTION_SEND_REQUEST_HEADERS
--> POST ... HTTP/1.1
Host: www.xxx.yyy.zzz
Connection: keep-alive
Content-Length: 105
Accept: */*
Origin: http://www.xxx.yyy.zzz
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36
X-Requested-With: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://www.xxx.yyy.zzz/app/resource/item/1
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
t=14736 [st= 0] HTTP_TRANSACTION_SEND_REQUEST_BODY
--> did_merge = true
--> is_chunked = false
--> length = 105
t=14736 [st= 0] -HTTP_TRANSACTION_SEND_REQUEST
t=14736 [st= 0] +HTTP_TRANSACTION_READ_HEADERS [dt=0]
t=14736 [st= 0] HTTP_STREAM_PARSER_READ_HEADERS [dt=0]
t=14736 [st= 0] HTTP_TRANSACTION_READ_RESPONSE_HEADERS
--> HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/7.5
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
X-Powered-By: ASP.NET
X-UA-Compatible: IE=9
Date: Tue, 28 Apr 2015 18:53:41 GMT
Content-Length: 1293
Edit
Playing around with different request from the console gives the following table of results (under Chrome). The current base URL is http://IPAddress
/app/topic/item and all test simply execute an $.ajax({ url: url, type: 'POST' })
+--------------------------------------+----------------------------+
| URL | Response |
+--------------------------------------+----------------------------+
| http://IP/app/topic/item/1/subitem/1 | net::ERR_INVALID_HANDLE |
| //IP/app/topic/item/1/subitem/1 | net::ERR_INVALID_HANDLE |
| /app/topic/item/1/subitem/1 | net::ERR_INVALID_HANDLE |
| 1/subitem/1 | net::ERR_INVALID_HANDLE |
| 1/foo | 404 (Not Found) [expected] |
| 1 | 302 (Redirect) [expected] |
+--------------------------------------+----------------------------+
Because the error only affects a subset of the POST action methods in one controller, I had initially thought this was a server-side issue, but after uncovering the issue of the missing Connection header, it actually appear to be a client-side issue. Exactly how the problem is triggered remains a mystery to me.
I did also verify that the Response headers for the working page and the problematic page are the same. Most relevant, the Persistent-Auth: true header is always returned in both cases.