Alex Pierstoval Alex Pierstoval - 1 year ago 421
Javascript Question

CORS with Symfony, jQuery, FOSRestBundle and NelmioCorsBundle

Edit (before reading the whole thing):

This issue is solved because I made a very stupid mistake in my code, not related to CORS or anything.

If you want to read this issue anyway, just note that it has a working CORS configuration, if you might want to take a good example.


I have a multi-domain Symfony application, and the back-end part updates data with web-services, not classic forms, for some reasons.

Webservices domain:

I am using jQuery to make AJAX calls to these webservices, and if I want to modify or create objects, I also send AJAX requests, the objects are merged with Doctrine entities and persisted.

I have been fighting for an entire year to make GET, PUT, POST and DELETE requests that would work properly on this application, and just because they're on different domains, I am forced to setup CORS on my different environments.


All jQuery AJAX request look like this:

ajaxObject = {
url: '' + uri,
type: method, // Can be GET, PUT, POST or DELETE only
dataType: 'json',
xhrFields: {
withCredentials: true
crossDomain: true,
contentType: "application/json",
jsonp: false,
data: method === 'GET' ? data : JSON.stringify(data) // Here, "data" is ALWAYS containing a plain object. If empty, it equals to "{}"

// ... Add callbacks depending on requests


Behind, the routes are managed with Symfony.

For CORS configuration, I am using NelmioCorsBundle with this configuration:

allow_credentials: true
origin_regex: true
- "^(https?://)?(back|api)\"
allow_headers: ['Origin','Accept','Content-Type']
allow_methods: ['POST','GET','DELETE','PUT','OPTIONS']
max_age: 3600
- "^(https?://)?(back|api)\"

The controller used is extending FOSRestBundle's one, has some security (for example, cannot POST/PUT/DELETE when you don't have the correct role), can update objects and returns only JSON data (there is a listener for that).

Ideal behavior

Ideally, I want this:

  1. Run the jQuery AJAX POST/PUT/DELETE request

  2. It has to send an OPTIONS request with all CORS headers

  3. NelmioCorsBundle should return the correct CORS headers accepting the request to be done, even before running any controller inside the app (made by the bundle's request listener)

  4. If accepted, the proper HTTP request is sent to the controller with all request data as a serialized JSON string (in the request payload), and Symfony retrieves it and interprets the correct JSON string as an array object

  5. The controller then gets the data, does its stuff, and returns an


But here, I tried maaaaany combinations, and I can't seem to make it work.

The point n°3 and 4 are failing, completely or partially.

Tried workarounds

  1. When I don't serialize the
    (see the Setup part above), the OPTIONS request is sent, but FOSRestBundle sends a
    Invalid json message received
    , and it's totally normal because the "request payload" (as seen in Chrome's developer tools) is the classic
    content, even if I specified
    contentType: "application/json"
    in the jQuery AJAX request, whereas it should be a serialized JSON string.

  2. However, if I do serialize the
    var, the "request payload" is valid, but the OPTIONS request is not send, making the whole request fail because of the lack of CORS acceptance.

  3. If I replace
    contentType: "application/json"
    , and don't serialize the data, then, the request payload is valid, but the OPTIONS request is not sent. It should also be normal, as explained in jQuery's docs under the contentType parameter:

    Note: For cross-domain requests, setting the content type to anything other than application/x-www-form-urlencoded, multipart/form-data, or text/plain will trigger the browser to send a preflight OPTIONS request to the server.

    But then, how to send a correct CORS request?


  • Where does this issue comes from?

  • Is this a problem with my setup? With jQuery?

  • With AJAX itself?

  • What can be the different solutions to solve this damn issue?

Edits (after comments)

2015-06-29 17:39


In AJAX, the
is set to
option is set to a serialized JSON string.

Preflight REQUEST headers

Access-Control-Request-Method: POST
Access-Control-Request-Headers: accept, content-type

Preflight RESPONSE headers

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, DELETE, PUT, OPTIONS
Access-Control-Allow-Headers: origin, accept, content-type
Access-Control-Max-Age: 3600

REQUEST headers (after preflight)

Content-Type: application/json
Cookie: mydomainPortal=v5gjedn8lsagt0uucrhshn7ck1
Accept: application/json, text/javascript, */*; q=0.01

Request payload (raw):

Must be noted that this throws a javascript error:

XMLHttpRequest cannot load No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '' is therefore not allowed access.

RESPONSE headers (after preflight)

HTTP/1.1 200 OK
Date: Mon, 29 Jun 2015 15:35:07 GMT
Server: Apache/2.4.12 (Unix) OpenSSL/1.0.2c Phusion_Passenger/5.0.11
Keep-Alive: timeout=5, max=91
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

2015-06-29 17:39

I also tried using vanilla javascript to make the XMLHttpRequest, the problem is still the same:

var xhr = new XMLHttpRequest;'POST', '', true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.withCredentials = true; // Sends cookie
xhr.onreadystatechange = function(e) {
// A simple callback;
// Now send the request with the serialized payload:
xhr.send('{"id":1,"name":"Updated test object"}');

Then, the browser sends an OPTIONS request:

Connection: keep-alive
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST

Which returns the correct Response headers:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, DELETE, PUT, OPTIONS
Access-Control-Allow-Headers: origin, accept, content-type
Access-Control-Max-Age: 3600

But then, the browser sends the POST request without any "Access-Control-*" header.

Answer Source

Actually, my setup worked well.
Absolutely perfectly, if I might say it.

It was just some kind of... Stupidity.

An exit; was hidden deep in a PHP file.

Sorry for annoyance. I guess it's kind of a record in a text/quality ratio on SO.

Edit: I still hope that this issue can be a proper example of a fully CORS-compatible Symfony project.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download