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:
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.
Some wild guesses:
This happens when you request a Role that is not in the Claims of the currently logged-in user. Verify that, if you're using [Authorize(Roles = "xyz")]
, the current user actually has that role.
It is not clear if your application uses a cookie for authentication. If yes, you should see it with the request. Are you setting withCredentials: true
for each request?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With