XmlHttpRequest CORS POST sent without cookies

All we need is an easy explanation of the problem, so here it is.

I have a Rails service returning data for my AngularJS frontend application. The service is configured to allow CORS requests by returning the adequate headers.

When I make a GET request to receive data, the CORS headers are sent, as well as the session cookie that I have previously received on login, you can see for yourself:

Request URL:http://10.211.194.121:3000/valoradores
Request Method:GET
Status Code:200 OK

Request Headers
Accept:application/json, text/plain, */*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Cache-Control:no-cache
Connection:keep-alive
Cookie:_gestisol_session=BAh7B0kiDHVzZXJfaWQGOgZFRmkASSIPc2Vzc2lvbl9pZAY7AEZJIiVmYTg3YTIxMjcxZWMxNjZiMjBmYWZiODM1ODQzMjZkYQY7AFQ%3D--df348feea08d39cbc9c817e49770e17e8f10b375
Host:10.211.194.121:3000
Origin:http://10.211.194.121:8999
Pragma:no-cache
Referer:http://10.211.194.121:8999/
User-Agent:Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
X-Requested-With:XMLHttpRequest

Response Headers
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:X-Requested-With,X-Prototype-Version,Content-Type,Cache-Control,Pragma,Origin
Access-Control-Allow-Methods:GET,POST,OPTIONS
Access-Control-Allow-Origin:http://10.211.194.121:8999
Access-Control-Max-Age:1728000
Cache-Control:max-age=0, private, must-revalidate
Connection:Keep-Alive
Content-Length:5389
Content-Type:application/json; charset=utf-8
Date:Mon, 04 Nov 2013 14:30:51 GMT
Etag:"2470d69bf6db243fbb337a5fb3543bb8"
Server:WEBrick/1.3.1 (Ruby/1.9.3/2011-10-30)
X-Request-Id:15027b3d323ad0adef7e06103e5aa3a7
X-Runtime:0.017379
X-Ua-Compatible:IE=Edge

Everything is right and I get my data back.

But when I make a POST request, neither the CORS headers nor the session cookie are sent along the request, and the POST is cancelled at the server as it has no session identifier. These are the headers of the request:

Request URL:http://10.211.194.121:3000/valoraciones

Request Headers
Accept:application/json, text/plain, */*
Cache-Control:no-cache
Content-Type:application/json;charset=UTF-8
Origin:http://10.211.194.121:8999
Pragma:no-cache
Referer:http://10.211.194.121:8999/
User-Agent:Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
X-Requested-With:XMLHttpRequest

Request Payload
{valoracione:{revisiones_id:1, valoradores_id:1}}
valoracione: {revisiones_id:1, valoradores_id:1}

And the service answers with a 403 because the request does not contain the session cookie.

I don’t know why the POST request fails, as the $resource is configured just like the other one and I have defined the default for $httpProvider to send the credentials (and it works right as the GET request succeeds):

  .config(['$httpProvider', function($httpProvider) {
     $httpProvider.defaults.withCredentials = true;
    }])

This is the failing resource when I call $save() on an instance:

'use strict';

angular.module('gestisolApp')
  .service('ValoracionesService', ['$resource', 'API_BASE_URL', function ValoracionesService($resource, API_BASE_URL) {
    this.valoraciones = $resource(API_BASE_URL + '/valoraciones');
  }]);

And this is the service that succeeds with the query() call:

'use strict';

angular.module('gestisolApp')
  .service('ValoradoresService', ['$resource', 'API_BASE_URL', function ValoradoresService($resource, API_BASE_URL) {
    this.valoradores = $resource(API_BASE_URL + '/valoradores');
  }]);

They are much like the same.

Does anybody know why the POST is sent without the session cookie?

Edit

Just to complete the information, preflight is handled by the following method, and is handled OK as the request before the failing POST is an OPTIONS that succeeds with a 200 response code:

def cors_preflight_check
        headers['Access-Control-Allow-Origin'] = 'http://10.211.194.121:8999'
        headers['Access-Control-Allow-Methods'] = 'GET,POST,OPTIONS'
        headers['Access-Control-Allow-Headers'] = 'X-Requested-With,X-Prototype-Version,Content-Type,Cache-Control,Pragma,Origin'
        headers['Access-Control-Allow-Credentials'] = 'true'
        headers['Access-Control-Max-Age'] = '1728000'
        render :nothing => true, :status => 200, :content_type => 'text/html'
end

This is the CORS OPTIONS request/response exchange previous to the failing POST:

Request URL:http://10.211.194.121:3000/valoraciones
Request Method:OPTIONS
Status Code:200 OK

Request Headers
Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:accept, x-requested-with, content-type
Access-Control-Request-Method:POST
Connection:keep-alive
Host:10.211.194.121:3000
Origin:http://10.211.194.121:8999
Referer:http://10.211.194.121:8999/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36

Response Headers
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:X-Requested-With,X-Prototype-Version,Content-Type,Cache-Control,Pragma,Origin
Access-Control-Allow-Methods:GET,POST,OPTIONS
Access-Control-Allow-Origin:http://10.211.194.121:8999
Access-Control-Max-Age:1728000
Cache-Control:max-age=0, private, must-revalidate
Connection:Keep-Alive
Content-Length:1
Content-Type:text/html; charset=utf-8
Date:Mon, 04 Nov 2013 15:57:38 GMT
Etag:"7215ee9c7d9dc229d2921a40e899ec5f"
Server:WEBrick/1.3.1 (Ruby/1.9.3/2011-10-30)
X-Request-Id:6aa5bb4359d54ab5bfd169e530720fa9
X-Runtime:0.003851
X-Ua-Compatible:IE=Edge

Edit 2: I have changed the title to reflect clearly my problem

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

I had a similar problem and adding the following before angular $http CORS request solved the problem.
$http.defaults.withCredentials = true;

Refer https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS#Requests_with_credentials for more details.

Method 2

When CORS is involved, then your browser will send an OPTIONS request before the POST request.

I don’t know the specifics with Rails, but I guess you have to configure Rails to actually answer the OPTIONS request with the adequate CORS headers.

The following code is just for comparison – it shows how you would address the issue in Java:

public void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setHeader("Access-Control-Allow-Origin", "http://10.211.194.121:8999");
    resp.setHeader("Access-Control-Allow-Credentials", "true");
    resp.setHeader("Access-Control-Allow-Methods", "OPTIONS, POST, GET");
    resp.setHeader("Access-Control-Allow-Headers", "X-Requested-With,X-Prototype-Version,Content-Type,Cache-Control,Pragma,Origin");
    resp.setHeader("Access-Control-Max-Age", "600");
    resp.setHeader("Access-Control-Expose-Headers","Access-Control-Allow-Origin");
    super.doOptions(req, resp);
}

But it might get you on the right track how to configure it in Rails.

Method 3

Ok, finally I figured out what was happening.

By the answer posted on this question, I removed the HttpOnly parameter from the cookie and got it working on Firefox. Later for Chrome was just a matter of applying the rest of recommendations from the answer to make it work, like setting a domain for the cookie.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply