16

Background

(If you are familiar with CORS, you might skip to the Question at the end)

CORS [1] is a solution to allow browsers to permit access to resources from different domains. E.g. REST data backends using AJAX.

It is well observed on the Internet that Chrome (and Webkit friends) do not handle HTTP authentication prompts when such a resource demands it. E.g. [2] ( this is to prevent the browser showing an auth login dialog on for example, a Web-Mail sign-in page when an image tags load fails with a 401 and needs credentials. The user may be duped in to entering their webmail credentials)

Chrome's behaviour in this situation is to drop the response, cancelling the request almost silently.

In fact, Chrome will only let the response through normally if a 200 occurs; any other status code is swallowed and the AJAX call is aborted.

If one uses jQuery to perform an Ajax request, and the remote site responds with an HTTP 401 status code, the request is cancelled and the ajax completes with an error, but the 401 code is missing. It as if the connection died or a equivalent lower layer problem.

The only website I've seen which mentions this caveat of CORS specifically is [1]

Browers don't do a good job of reporting what went wrong when there is an error. For example, Firefox reports a status of 0 and an empty statusText for all errors. Browsers also report an error message to the console log, but this message cannot be accessed from JavaScript. When handling onerror, you will know that an error occurred, but not much else.

You can disable this security by using a chrome startup flag - just to test both the backend is functioning 'normally' and one sanity hasn't been unduly affected by the loss of proper status codes. See [2]

Problem

What if the backend and front-end client code is supposed to use the 401 as part of it's authentication process? - any failures are just just mangled into one "error" output.

For example: Upon issuing a request to authenticate - e.g. 2-legged OAuth token request -against a RESTful backend - which uses proper status codes - The client has no idea if:

  • 401 The user was not authenticated.
  • --- The connection failed.
  • 400 There was a bad request.
  • 500 The server has a problem.
  • 207 There was no content after success.

A backend of ours which was written quite RESTfully, following as many best practices as was deemed cost effective at the time; one of these was relying on proper HTTP status codes. The Java REST Client we have working against has worked great.

Now I have begun creating a Javascript client, for embedding in webpages - specifically using CORS, and we have hit the problem in Chrome etc. (Note Firefox seems fine, and we generally don't care for IE at present.)

Question

What have people done to work around these issues?
Is there anything that can be done to make CORS more suitable for trusted backends?

Links

  1. CORS Tutorial: http://www.html5rocks.com/en/tutorials/cors/
  2. Chrome won't allow 401-triggered auth prompt for resources: http://code.google.com/p/chromium/issues/detail?id=81251
  3. --disable-web-security for Chrome: Disable same origin policy in Chrome
Community
  • 1
  • 1
Rob Shepherd
  • 801
  • 8
  • 22

3 Answers3

6

Just ran into this issue. Setting the HTTP headers for the 401 response did the trick for me. The library I was using wasn't doing this properly without some customization. e.g.:

  self.headers["Access-Control-Max-Age"] = '1728000'
  self.headers["Access-Control-Allow-Origin"] = "http://localhost:3001"
  self.headers["Access-Control-Allow-Methods"] = "ANY"
  self.headers["Access-Control-Allow-Credentials"] = 'true'
etipton
  • 144
  • 1
  • 5
1

I spend a couple of hours today hunting for the answer to this, while developing a web app on Chrome. Some others have written fairly detailed analyses of this situation.

There were two potential issues:

  1. It could be that Chrome was doing a preflight and was getting a non-200 or a non-CORS response in response

  2. For some reason my non-200 (error) status codes were not getting the headers attached. As I found out in this post, this second reason was the issue. Essentially, NGINX only adds headers on successful responses by default. To get my error responses through CORS, it sufficed to change

add_header 'Access-Control-Allow-Origin' '*';

to

add_header 'Access-Control-Allow-Origin' '*' always;

As the article notes, you may also have to add always to 'Access-Control-Allow-Credentials' and 'Access-Control-Allow-Headers'. After doing this, all error codes also went through without CORS issues. It may be that the program you are using for API serving does the same. Hope this helps and saves someone some time!

Andrew Ma
  • 171
  • 8
-3

Now after much fiddling, I've managed to make it work. blush

There seems to be an awful lot of voodoo which makes up the exact scenario that ensures CORS will work in each browser, however having traced back numerous issues like:

  • Nginx, by default, only uses HTTP/1.0 in it's reverse proxy by default
  • Tomcat silently ignores duplicate init-param declaration with the same key, but jetty combines them (so CORS filter was off in server but on in dev-laptop), and nginx was overwriting some headers.

Having stumbled upon what seemed to be a complete no-go, I ended up writing a CORS 4xx->200 wrapper as a jquery plugin,in an attempt to solve the issue. Debugging that plugin led me to my eventual fix.

Rob Shepherd
  • 801
  • 8
  • 22