Jul 10
How to get Cookies working with CORS, jQuery and xhr AJAX
In one sentence: Make sure you have "third party cookies" enabled/allowed in your browser settings!
I can't stress this more, I lost hours of bug hunting until I finally thought of this setting, which was the root of my problem. It was this Mozilla bug report which directed me into the right direction. It tells you that your browser cookie policy (obviously) influences how the browser handles Cross-Origin-Resource-Sharing and thus, if cookies are sent or accepted.
My situation was this: I had Javascript, which was using jQuery to access a remote api, on a different server/host, to pull some data from. The API needs to know a user id, and does authentication by relying on a Cookie - so, my XMLHttpRequest (xhr) client needs to be logged in on the other sever. A pretty common setup. But, it was not working for me!
I've read probably a hundred blog posts, tutorials and stackoverflow babble on how to get CORS right. Some better, some worse. Some struggle with early, before 2008, implementations, others with bugs in < 1.5 jQuery, and others with setting the right CORS headers or simply with not knowing what they are doing - finally resorting to throwing all METHODS back in OPTIONS preflights, giving all sorts of Headers in Access-Control-Allow-Headers, setting wildcards whenever possible, undermining CORS and preflight whenever possible. Bad, bad. Some even changed the Content-Type of the xhr object to disable the idea of CORS alltogether. I would suggest to simply follow the MDN guide and that's it. There you'll also find out that asking for credentials *and* setting wildcards is not possible...
If you want to see a minimal CORS example in action, try this node.js CORS test. There's also a PerlDancer based fork. If you want to avoid a local install, try test-cors.org (github)
This test also illustrates, that the problem in getting credentialed requests working is not with jQuery. Period. All issues straightened out long ago. jQuery handles CORS properly since version 1.5. And yes, the jQuery xhr object is the same as the browser itself - any cookie the xhr object receives will be set on the (your) browser and the other way round, if the (your) browser has a cookie set for a certain domain, the xhr object will carry it along if the cross-domain request is for a domain with a matching cookie entry.
That said, given that you have Access-Control-Allow-Credentials header set, and you have configured jQuery's xhr to send credentials with xhrFields: { withCredentials: true }.
Forget about the now faded trick of having a beforeSend callback where you set xhr into withCredentials mode, and forget about crossDomain : true as this is (read the docs) for forcing cross-domain behaviour on the same host, probably for debugging.
My case
My case now was, I had CORS working, but whenever I dug into Firebug to see if the Credentials end up on the xhr API request and its header: no credentials: no Cookie: header! But whenever I did the same request in the browser directly, by typing the API endpoint into the address-bar, as it was a GET this was easily possible, the Cookie was appended to the header, as expected.
I kept banging my head against it. All sorts of ideas, a typo, a bug, some internal policy within jQuery's xhr that was preventing this, or Firefox 30 - which I was on, having introduced more rigid policies (it has not, btw), or something else I had configured in a wrong way so the xhr ended up having no Cookie - but I was wrong! Read the first sentence.
Basic Auth works!?
One strange thing I encountered was when I changed access control on the API from being Cookie based to Basic Authentication - which is just as well possible. Strangely this worked! The Javascript ran, issued the xhr request and when the xhr reached the 401 auth required response, it prompted me to enter credentials. I did that, and the communication went on, uninterrupted and as expected, faithfully sending the Authorization header. With every xhr request - like, as a joke!
Could it be, this Access-Control-Allow-Credentials header only includes basic Auth?? Why is Authorization being passed through when Cookies, being set for the same domain, are not?? Well, read the first sentence: because my browser setting told my browser/xhr-object so!
So the explanation why Basic auth works is that it is not influenced by the cookie policy - obviously. And when I enabled cookies for third parties, even when done conservatively, "only for parties I have visited" (which I have when I have logged into the other domain) the xhr trustfully transmits the Cookie: header with every request...
So, again: Make sure you have "third party cookies" enabled/allowed in your browser settings when you want CORS to work!
Just as a note, you can use JSONP for cross-domain calls, and avoid some of the CORS troubles - but not the "3rd party cookie policy", it applies to all calls, also JSONP....
September 30th, 2015 at 5:06 pm
Please add the full date to your posts. This looks like good information, but without the year I don't know if it's 1 year old or 5 years old. Date is my first hint on how useful this article will be.
November 20th, 2015 at 1:04 pm
Yes, quite right! Time is now properly displayed. Thanks.
September 30th, 2015 at 5:06 pm
Please add the full date to your posts. This looks like good information, but without the year I don't know if it's 1 year old or 5 years old. Date is my first hint on how useful this article will be.
November 20th, 2015 at 1:04 pm
Yes, quite right! Time is now properly displayed. Thanks.