2

I hope this question isn't too vague, but I feel like I'm missing something fundamental. Here is the situation I'm in. We have a thick client application that runs on each user's local machine (Windows 7) and provides a JavaScript API to interface with the app. We have a web app that is hosted on a server, but makes AJAX requests to localhost to interface with the thick clients JS API.

On one machine, we load the web app. All AJAX requests made to localhost are made with no OPTIONS preflight requests. The net tab of the JS console shows the POSTs being made, and the responses are successful (I get to tell the thick client what headers to include in the response, including all necessary CORS headers).

On another machine, we load the web app from the same server. However, when AJAX requests are made to localhost, the the (expected) OPTIONS request is made. Unfortunately, the thick client doesn't respond with the appropriate Access-Control-Allow-Origin header, so the subsequent POST is not allowed to be made.

The browsers don't matter, we've tested the latest versions of Firefox and Chrome and we always get the same behavior from the machines. We have tested on 4 machines -- 2 send the OPTIONS, 2 just send the POST with no preceding OPTIONS. It seems like it's up to the browser as to whether or not the AJAX destination differs from the origin and whether or not it send a preflight request. I can't for the life of my figure out why my machine doesn't send out any OPTIONS requests, even though the AJAX requests (to either localhost or lvh.me) are a different host/domain than the source of the web page, yet loading the same page from another machine does produce them.

Is there some sort of browser setting that effects this? Or a Windows setting? Why would we ever see different behavior on this?

UPDATE1:

To simplify and clarify, I have two machines, machineA and machineB. Both machines load a web app from a remote server, example.com. Both machines have a full blown thick application installed on their local machines that provides a web server and API. The web app that gets loaded from example.com contains JavaScript code to submit AJAX requests to localhost, so that each user can interact with their own local instance of the thick client application. Since localhost != example.com, the requests should be considered cross-domain.

When machineA submits a standard jQuery AJAX request to localhost (of which, the payload of the POST is simply "true", which will cause the thick client to echo the value back -- just used for polling to determine if the thick client is even available), the POST is made directly with no OPTIONS request: POST http://localhost:4000/cgi-bin/Extensions/GeoLinkConsole/evaljs.html 200 OK 0ms Interestingly, the response from that POST must include the Access-Control-Allow-Origin header with example.com in order to avoid a CORS error.

When machineB loads the same page from example.com, submits the same standard jQuery AJAX request to localhost, with the same "true" payload, the browser submits an OPTIONS request. This is the behavior I would actually expect. The OPTIONS request response with a 200 OK, however it doesn't include the Access-Control-Allow-Origin header, so it prevents the POST from going through. That is not my main concern and is obviously an issue with the thick client. My biggest question is why some machines are generating the OPTIONS and some aren't. I hope this helps explain.

Update 2:

I'm including both sets of request headers. I wasn't aware that the actual request header could affect whether or not an OPTIONS request would be generated. Right off the bat, I see that the one that is sending the OPTIONS includes both an Acces-Control-Request-Headers and Access-Control-Request_Method that the other one doesn't...

Request headers from the machine that sends the POST with no OPTIONS:

Accept:    text/plain, */*; q=0.01
Accept-Encoding:    gzip, deflate
Accept-Language:    en-US,en;q=0.5
Content-Length:    354
Content-Type:   text/plain; charset=UTF-8
Host:    localhost:4000
Origin:    http://example.com:7802
Referer:    http://example.com:7802/
User-Agent:    Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0

Request headers from machine that sends OPTIONS:

Accept:    */*
Accept-Encoding:    gzip, deflate, sdch
Accept-Language:    en-US,en;q=0.8
Access-Control-Request-Headers:    accept, content-type, x-csrftoken
Access-Control-Request-Method:    POST
Connection:    keep-alive
Host:    localhost:4000
Origin:    http://example.com:7802
Referer:    http://example.com:7802/
User-Agent:    Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36

In addition, the machine has a "general" headers block -- I'm assuming the request headers I just posted were for the original POST request and these relate to the preflight OPTIONS request:

Request URL:    http://localhost:4000/cgi-bin/Extensions/GeoLinkConsole/evaljs.html
Request Method:   OPTIONS
Status Code:    200 OK
Remote Address:    127.0.0.1:4000
Bal
  • 2,027
  • 4
  • 25
  • 51
  • Thanks apsillers. I posted an update. I hope it clarifies my issue -- I know it's a bit confusing. – Bal Jan 06 '16 at 15:56
  • @apsillers Ok -- see update 2, I included all headers. I'm not quite understanding why extra headers are being appended on only certain machines, when all machines are loading the same web page from the same server. – Bal Jan 06 '16 at 16:39
  • Just FYI -- "* I wasn't aware that the actual request header could affect whether or not an OPTIONS request would be generated*" -- this is one of the *only* things (along with HTTP method) that can influence whether a preflight is sent. – apsillers Jan 06 '16 at 16:43
  • Great to know... so is the solution to preventing the OPTIONS request in this situation trying to remove the Access-Control-Request headers from the request, or adding different values to them? If you respond in an answer, I'll mark it correct. Thanks a bunch for your help! – Bal Jan 06 '16 at 16:47
  • So, the only difference this that the second machine wants to include a non-simple header called `x-csrftoken` in its request. Find out *why* that is and you might solve your problem. The solution is to either (1) alter the thick client to correctly tolerate a `x-csrftoken` header and send back an appropriate `Access-Control-Allow-Headers` and `Access-Control-Allow-Origin` in response to the OPTIONS, or (2) to stop the client code from trying to send `x-csrftoken`. (Also: the info in your final code block isn't headers. They're non-header parts of the HTTP request.) – apsillers Jan 06 '16 at 16:47
  • That did it! Another developer added code to add the x-csrftoken to every AJAX request in jQuery. Why does my machine not actually add that during the request? I have no idea. However, we removed it and now everything works without the OPTIONS request. Thanks so much! Again, post that as an answer and I'll mark it correct :) – Bal Jan 06 '16 at 17:02

1 Answers1

5

(You may be interested in reading the "non-simple" potions of both my answer on How does Access-Control-Allow-Origin header work? the HTML5 writeup on CORS to learn generally why OPTIONS preflight requests happen.)

As you can see, the OPTIONS request includes Access-Control-Request-Headers which includes the header name x-csrftoken. This means that the JavaScript is trying to send a header called x-csrftoken. Since this is not a simple CORS header (i.e., Accept, Accept-Language, Content-Language, and sometimes Content-Type), it must first be allowed by a preflight OPTIONS request.

In addition to the normal Access-Control-Allow-Origin header, the server should respond to the OPTIONS preflight with an Access-Control-Allow-Headers header that includes the x-csrftoken header name. Once that is done, the browser will allow the actual POST request to go through to the server.

Alternatively, remove the non-simple x-csrftoken header altogether, and no preflight will be necessary.

Community
  • 1
  • 1
apsillers
  • 112,806
  • 17
  • 235
  • 239