14

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

  • Bit of a shot in the dark here, but are you handling the pre-flight request (on the server)? That's sent using the `OPTIONS` verb. *Edit*: Hmmm, not 100% sure pre-flight applies to `POST`, but possibly might depending on headers. – T.J. Crowder Nov 04 '13 at 14:55
  • Preflight applies, or at least the preflight is done correctly and returns with response code 200. The problem is the following POST goes without any "Allow-*" header nor cookie. – Damián Serrano Thode Nov 04 '13 at 15:31

3 Answers3

15

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.

user2508245
  • 151
  • 2
  • 1
    This is correct, along with ensuring the backend allows credentials & specifies specific allowed origin's (ie not '*') – unohoo Jul 05 '14 at 12:55
  • 1
    This solved it for me! Ensure you also return a 'Access-Control-Allow-Credentials' header from your server, with the value set to true – Jonathon Blok Aug 06 '14 at 15:25
  • This worked. Whereas setting a config object "configObj = { withCredentials: true };" as a parameter to $http did not. Thx for this! – nuander Jun 24 '15 at 20:21
5

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.

marty
  • 3,965
  • 20
  • 19
  • I have all the CORS stuff implemented in my Rails controller, this part works Ok. The only differing part is that I have no Access-Control-Expose-Headers header in my CORS response. – Damián Serrano Thode Nov 04 '13 at 15:32
  • All browsers that support CORS make the OPTIONS request before making the actual cross-domain request you are attempting to make. This is to check the capabilities on the server and avoid sending a payload that might not be allowed. @Marty is correct, you will need to enable this on your server. More that here: http://www.html5rocks.com/en/tutorials/cors/#toc-handling-a-not-so-simple-request – joseeight Nov 04 '13 at 15:37
  • Yes, as I commented above at the question, the preflight is done successfully and returns with response code 200, but the following POST request goes without "Allow-*" headers, nor cookies. – Damián Serrano Thode Nov 04 '13 at 15:43
  • I have included additional information: the CORS handling method and the OPTIONS request and response exchange prior to the POST – Damián Serrano Thode Nov 04 '13 at 16:08
  • it might be worth checking @JStark 's response in http://stackoverflow.com/questions/12111936/angularjs-performs-an-options-http-request-for-a-cross-origin-resource?rq=1 – marty Nov 04 '13 at 17:07
  • I think it does not apply to my problem as in my case the OPTIONS is performed correctly but the following POST fails because it does not include the session cookie – Damián Serrano Thode Nov 04 '13 at 17:35
2

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.

Community
  • 1
  • 1