Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JQuery stuck at CORS preflight and IIS ghost response

I'm stuck. Seriously... - solved. read on :)

Scenario: I'm trying to do the right thing here. I added CORS functionality to my REST Service (ASP.NET Web-API) relying on the Thinktecture Identitymodel CORS DelegatingHandler. So far so good.

To actually test if it is working I did the following:

  1. I set up a simple HTML page and published it on a different host than the rest service (xttp://otherhost/simplewebpage). The page uses JQuery to make the sample request. Code see below.
  2. Next I set up my rest service to not use iis express but rather the fully blown instance of it running on my development machine (xttp://developmenthost/restservice).
  3. Last but not least on my development machine I open up the xttp://otherhost/simplewebpage and fire the Ajax request. The error callback is executed telling me there was a "transport error" (IE9) or "" (empty string) in Chrome. I made sure there's no proxy related connectivity issue or anything like that.

So I went forth and looked at the Fiddler traces and IIS logs. Fiddler says there is no GET /rest/hello request but rather a OPTIONS /rest/hello request - which is totally fine and expected! However the response to the OPTIONS request is rather intriguing!

The whole response header looks like this:

HTTP/1.1 200 OK Allow: OPTIONS, TRACE, GET, HEAD, POST Server: Microsoft-IIS/7.5 Public: OPTIONS, TRACE, GET, HEAD, POST Date: Fri, 15 Feb 2013 14:09:27 GMT Content-Length: 0 

This is of course nowhere even close to the expected response. The fun part about this is, that the Request didn't even hit Application_BeginRequest() in my application. So there's no way my application could be responsible for that result. I can see the request in my IIS logs and IIS adds the Powered-by-ASP.NET header.. so it definitely passes through (the right) IIS site.

The JQuery code that triggers the ajax request:

    function Run()     {         $.ajax({             type: 'GET',             url: url,             dataType: "json",             beforeSend: function(jqXhr) {                 jqXhr.setRequestHeader("Authorization", "Basic " + getBasicHttpEncodedString(userName, password));                 jqXhr.setRequestHeader("Api-Key", "123");             },             success: successCallback,             error: errorCallback,             timeout: 180*1000         });     } 

The resulting OPTIONS request looks like that:

OPTIONS http://services.dev13/Rest/Hello HTTP/1.1 Host: developmenthost Connection: keep-alive Access-Control-Request-Method: GET Origin: http://otherhost/simplewebpage User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17 Access-Control-Request-Headers: accept, origin, api-key, authorization Accept: */* DNT: 1 Referer: http://otherhost/simplewebpage Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 

... and you have already seen the response to that above.

Any idea who exactly answers my OPTIONS request? Or is my JQuery code flawed? The REST Service works just fine if I use for example Postman (Google Chrome App) or if I forge Requests in Fiddler (That's probably because they don't do CORS negotiation - there's no OPTIONS request).

Update #1: Earlier today I read somewhere that disabling WebDAV is mandatory because it interferes with OPTIONS requests. My IIS Role Services view tells me WebDAV Publishing is Not installed.

* Update #2:* Problem solved?? I dug deeper. There's a module registered in IIS that is responsible for the "undesired(?)" response to the OPTIONS request. Its name is "OPTIONSVerbHandler" (handler: ProtocolSupportModule). If I disable that module the request passes through to my application. There a more meaningful response is created and then followed by the actual GET request! YAY!

HTTP/1.1 200 OK Cache-Control: no-cache Pragma: no-cache Expires: -1 Server: Microsoft-IIS/7.5 Access-Control-Allow-Origin: http://otherhost/simplewebpage Access-Control-Allow-Credentials: true Access-Control-Allow-Headers: accept,origin,api-key,authorization X-AspNet-Version: 4.0.30319 Date: Fri, 15 Feb 2013 15:09:25 GMT Content-Length: 0 

Once you know where the problem is of course you find plenty of resources telling you to make sure your web.config looks like that :-/

<system.webServer>     <validation validateIntegratedModeConfiguration="false" />     <modules runAllManagedModulesForAllRequests="false">       <remove name="WebDAVModule" />     </modules>     <handlers>       <remove name="OPTIONSVerbHandler" />       <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />       <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />       <remove name="ExtensionlessUrlHandler-Integrated-4.0" />       <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />       <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />       <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />     </handlers>   </system.webServer> 

It doesn't still work in IE9 though ("error: no transport"). In case someone went down the same road as I did -> it's a IE9 thing: https://stackoverflow.com/a/10232313/1407618

like image 828
lapsus Avatar asked Feb 15 '13 14:02

lapsus


People also ask

How to skip CORS Preflight request?

Simple Requests. Another way to avoid Preflight requests is to use simple requests. Preflight requests are not mandatory for simple requests, and according to w3c CORS specification, we can label HTTP requests as simple requests if they meet the following conditions. Request method should be GET , POST , or HEAD .

Can we stop Preflight request?

Yes it's possible to avoid options request. Options request is a preflight request when you send (post) any data to another domain. It's a browser security issue.

What is Preflight request in chrome?

Preflight requests are a mechanism introduced by the Cross-Origin Resource Sharing (CORS) standard used to request permission from a target website before sending it an HTTP request that might have side effects.


1 Answers

Create PreflightRequestsHandler class where you allow request headers(1) and enable cors before your class(2).

1. public class PreflightRequestsHandler : DelegatingHandler     {         protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)         {             if (request.Headers.Contains("Origin") && request.Method.Method.Equals("OPTIONS"))             {                 var response = new HttpResponseMessage { StatusCode = HttpStatusCode.OK };                 // Define and add values to variables: origins, headers, methods (can be global)                                response.Headers.Add("Access-Control-Allow-Origin", "*");                 response.Headers.Add("Access-Control-Allow-Headers", "content-type");                 response.Headers.Add("Access-Control-Allow-Methods", "*");                 var tsc = new TaskCompletionSource<HttpResponseMessage>();                 tsc.SetResult(response);                 return tsc.Task;             }             return base.SendAsync(request, cancellationToken);         }      }  2. [EnableCors(origins: "*", headers: "*", methods: "*", exposedHeaders: "X-Custom-Header")] 
like image 182
Kalyani Rathore Avatar answered Sep 20 '22 08:09

Kalyani Rathore