1

I'm trying to implement a cross-domain setup for my backbone app.

my server (express.js) is allowing cross domains and credential:

var allowCrossDomain = function(req, res, next) {
  var allowedHost = [
    'http://localhost:3001',
    'http://localhost:7357'
  ];

  if(allowedHost.indexOf(req.headers.origin) !== -1) {
    res.header('Access-Control-Allow-Credentials', true);
    res.header('Access-Control-Allow-Origin', req.headers.origin)
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
    res.header('Access-Control-Allow-Headers', 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version');
    next();
  } else {
    res.send({auth: false});
  }
}

app.configure(function(){
    ....
    app.use(allowCrossDomain);
    ....
});

my client (backbone.js) is configured to accept cross domain as well :

define(["backbone", "jquery", "underscore"], function (BB, $, _) {
  return BB.Model.extend({

    idAttribute: "_id",

    initialize: function () {
      var that = this;

      $.ajaxPrefilter( function( options, originalOptions, jqXHR ) {
        options.crossDomain ={
          crossDomain: true
        };
        options.xhrFields = {
          withCredentials: true
        };
      });
    }
  });
});

Now when I test my code (let say a POST request), i have a very special behavior:

var contacts = new Contacts;
contacts.create({'name': 'my name'});

The browser return this message:

OPTIONS ... 404 (Not Found) jquery.js:8419

This totally confuse me as the OPTIONS http method is not supported by backbone?

josh3736
  • 139,160
  • 33
  • 216
  • 263
Michael
  • 2,436
  • 1
  • 36
  • 57
  • The OPTIONS call is the preflight request: https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS?redirectlocale=en-US&redirectslug=HTTP_access_control – Werner Kvalem Vesterås Jan 06 '13 at 18:59

1 Answers1

7

Most likely, your Express routes only specify GET and/or POST methods. For example,

app.post('/some/api/method', function(req, res) { ... });

This means you've only defined a route handler for POSTs to /some/api/method, and requests using any other method (like GET or OPTIONS) will return a 404.

In certains cituations (such as sending custom HTTP headers), XHR requests to a cross-origin URL using CORS requires that the browser must first issue an OPTIONS request to see if cross domain requests are allowed. Only if the OPTIONS request succeeds (HTTP 200 with CORS headers) will the browser make the actual request.

Since you've only defined a POST route on your server, the OPTIONS request fails and the browser doesn't make the request. You need to properly respond to the OPTIONS request:

app.options('/some/api/method', function(req, res) {
    // At this point, the `allowCrossDomain()` middleware will already have
    // taken care of the CORS stuff, so just return OK.
    res.send(200);
});

Now, the preflight OPTIONS check will pass, so the real request to the POST handler will be made.


Bonus comments regarding your code:

  • Performance improvements:
    • Use an object to lookup and validate origins rather than an array. Using indexOf requires slow iteration of the array on every request, where using an object allows quick lookup. (Remember that JavaScript objects store their keys in a dictionary-like data structure.)
    • Define allowedHosts outside the validation function – they don't change, so there's no need to create a new object each time allowCrossDomain is invoked (requiring garbage collection).
  • You should probably send a non-success HTTP response code when the cross-origin check fails.

 

var allowedHost = {
  'http://localhost:3001': true,
  'http://localhost:7357': true
};

var allowCrossDomain = function(req, res, next) {
  if(allowedHost[req.headers.origin]) {
    res.header('Access-Control-Allow-Credentials', true);
    res.header('Access-Control-Allow-Origin', req.headers.origin)
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
    res.header('Access-Control-Allow-Headers', 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version');
    next();
  } else {
    res.send(403, {auth: false});
  }
}
josh3736
  • 139,160
  • 33
  • 216
  • 263