CORS - request made after preflight request expects 'Access-Control-Allow-Credentials' to be true

16.1k Views Asked by At

As always I will try to put my questions sweet and clean, as much as I can.

I have to send CORS as a requirement request and I'm doing it through $.ajax and it looks like this:

$.ajaxSetup({
        beforeSend: function (jqXHR, options) {
            options.url = Utils.setUrlHostname(options.url);
            options.xhrFields = {
                withCredentials: true
            };
            jqXHR.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        }
    });

On the server side (php - zend framework) I'm handling headers like this:

public function OPTIONS_ProxyAction()
    {
        $this->getResponse()->setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, POST, PUT, DELETE');
        $this->getResponse()->setHeader('Access-Control-Allow-Headers', 'Content-Type, x-requested-with');
        $this->getResponse()->setHeader('Access-Control-Allow-Credentials', 'true');
        $this->getResponse()->setBody(null);
        $this->_helper->viewRenderer->setNoRender(true);
    }

The preflight request headers looks fine and I'm getting 200 OK:

Request Headers:

Host: domain1111.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:38.0) Gecko/20100101 Firefox/38.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: https://domain2222.com
Access-Control-Request-Method: GET (this is seen as OPTIONS in network tab)
Access-Control-Request-Headers: x-requested-with
Connection: keep-alive
Cache-Control: max-age=0

And Response Headers:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: X-Requested-With, Content-Type
Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, DELETE
Access-Control-Allow-Origin: https://domain2222.com
Cache-Control: max-age=0, s-maxage=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection: Keep-Alive
Content-Length: 0
Content-Type: text/html
Date: Thu, 11 Jun 2015 08:40:42 GMT
Etag: "d41d8cd98f00b204e9800998ecf8427e"
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Keep-Alive: timeout=5, max=100
Pragma: no-cache
Server: Apache
Vary: X-CDN
X-Frame-Options: SAMEORIGIN
X-Ua-Compatible: IE=edge,chrome=1

I'm getting empty response which is fine, and a new request is sent to the same address, but the method is GET instead of OPTIONS and it looks like this:

Request Headers:

Host: domain1111.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:38.0) Gecko/20100101 Firefox/38.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Referer: https://domain2222.com/some/request
Origin: https://domain1111.com
Cookie: s1=533C36A9095C003A; 
BGUID=some complicated guid here
Connection: keep-alive
Cache-Control: max-age=0

And Response:

Access-Control-Allow-Origin: https://domain2222.com
Cache-Control: max-age=0, s-maxage=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection: Keep-Alive
Content-Length: 3278
Content-Type: application/json
Date: Thu, 11 Jun 2015 08:40:43 GMT
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Keep-Alive: timeout=5, max=99
Pragma: no-cache
Server: Apache
Vary: X-CDN
X-Frame-Options: SAMEORIGIN

The problem is that after the real request is sent, I'm getting in Firefox:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://domain1111.com/some/request. (Reason: expected 'true' in CORS header 'Access-Control-Allow-Credentials')

And similarly in Chrome:

XMLHttpRequest cannot load https://domain1111.com/some/request. Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is ''. It must be 'true' to allow credentials.

Those errors occur when the second CORS request is finished. So after the first preflight request is sent, it returns with 200 OK and an empty response, which is fine; after the second request is sent I'm getting the errors listed above, with no data returned.

Additionally, the Response tab in Firefox is giving me this message:

SyntaxError: JSON.parse: unexpected end of data at line 1 column 1 on the JSON data

I'm not sure whether during second request the property 'withCredential' is sent as well or not, and maybe the server is not returning:

'Access-Control-Allow-Credentials', 'true'

Why it's happening only with OPTIONS requests?

Should I return those headers from the server, no matter what request method is? Or maybe there is something wrong with ajaxSetup? Maybe it shouldn't sent 'withCredentials: true' within the second CORS request?

Update:

The problem has been fixed by adding Access-Control-Allow-Credentials: true, to all responses returned by the server (PUT, GET, POST, DELETE, OPTIONS)

3

There are 3 best solutions below

4
On

I think you need to return the CORS policy for all requests (pre-flight and normal). I also think this behaviour changed. I remember implementing a CORS policy on just the pre-flight request.

I can't be certain but I think browsers also behave differently. For instance, Chrome now always sends the Origin header, not just on the pre-flight request.

My current implementation looks (only) for the Origin header, and if a CORS policy is required (Origin != domain) return it.

I've haven't found any definitive guideline or standards document on how to implement CORS properly.

0
On

According to the W3C CORS docs, §7.1.5 step 3, both the preflight and actual requests must perform a "resource sharing check". From §7.2 where that check is defined:

  1. If the response includes zero or more than one Access-Control-Allow-Origin header values, return fail and terminate this algorithm.

[..]

  1. If the omit credentials flag is unset and the response includes zero or more than one Access-Control-Allow-Credentials header values, return fail and terminate this algorithm.

In otherwords, both preflight and actual responses must contain both the Access-Control-Allow-Origin header and, if it is used, the Access-Control-Allow-Credentials header.

0
On

The header is only returned for your OPTIONS requests. OPTIONS_ProxyAction() This header Access-Control-Allow-Credentials: true needs to be maintained for all CORS requests (GET,POST...) when the parameter withCredentials: true is sent.

For more information on the parameter

options.xhrFields = {
        withCredentials: true
 };

and the header Access-Control-Allow-Credentials look at this article:

http://www.ozkary.com/2015/12/api-oauth-token-access-control-allow-credentials.html

hope it helps.