11

I am using a third party library which spawns a raw XMLHttpRequest with new XMLHttpRequest.

This bypasses my CSRF protection and gets shot down by my rails server.

Is there a way to globally add a predefined CSRF token ($('meta[name=csrf-token]').attr('content')) to ALL instances of XMLHttpRequest at instantiation time?

Abraham P
  • 15,029
  • 13
  • 58
  • 126
  • did you try [`$.ajaxPrefilter`](http://api.jquery.com/jQuery.ajaxPrefilter/)? and here's one example: [Adding CSRF token to jQuery AJAX requests](http://avinmathew.com/adding-csrf-token-to-jquery-ajax-requests/) – shawnzhu Jun 13 '14 at 00:55
  • 1
    Why do *you* want to fix third-pary libraries that don't know how to contact your api properly? – Bergi Jun 13 '14 at 01:08
  • @shawnzhu: No, OP says it's a *raw* `XMLHttpRequest`! – Bergi Jun 13 '14 at 01:13

3 Answers3

29

I'd recommend to intercept calls to the send method:

(function() {
    var send = XMLHttpRequest.prototype.send,
        token = $('meta[name=csrf-token]').attr('content');
    XMLHttpRequest.prototype.send = function(data) {
        this.setRequestHeader('X-CSRF-Token', token);
        return send.apply(this, arguments);
    };
}());

This won't add the header at instantiation time, but right before the request is sent. You can intercept calls to new XMLHttpRequest() as well, but that won't be helpful as you need to wait with adding the header until open was called.

You might also want to include a test for the target URL of the request, so that you only add the header when your own api is called. Not doing so might leak the token elsewhere, or might even break cross-domain CORS calls that don't allow this header.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • i went with doing it after open so that it can be clobbered, but 6 of one, half a dozen of the other i suppose... – dandavis Jun 13 '14 at 01:24
  • @dandavis: initially I thought to intercept `open` as well, but I didn't just want to repeat the code from your answer. You got a +1 just as I saw your post pop up :) – Bergi Jun 13 '14 at 01:31
  • great minds think alike. can i ask, are you 100% ok with this sort of mod? it feels a little risky/dirty to me, even though i can't see the harm. – dandavis Jun 13 '14 at 01:35
  • 99%. Same risks as always when messing with builtin objects: they might be implemented differently than you thought. From instance-specific methods to non-writable `.send` properties, to the `send` method not inheriting `.apply` all quirks is possible (old IEs were known to have such oddities in their DOM). However, in modern browsers this will work, they respect the Web-IDL spec for how to implement the HTML5 apis in javascript. – Bergi Jun 13 '14 at 01:42
5

you can wrap the ajax open() method to open and then set the header right away:

(function() {
    var op = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function() {
        var resp = op.apply(this, arguments);
        this.setRequestHeader('X-CSRF-Token', $('meta[name=csrf-token]').attr('content'));
        return resp;
    };
}());
dandavis
  • 16,370
  • 5
  • 40
  • 36
4

If you need a Jquery independent solution you could use:

  (function() {
      var send = XMLHttpRequest.prototype.send,
          token = document.getElementsByTagName('meta')['csrf-token'].content;
      XMLHttpRequest.prototype.send = function(data) {
          this.setRequestHeader('X-CSRF-Token', token);
          return send.apply(this, arguments);
      };
  }());
ErvalhouS
  • 4,178
  • 1
  • 22
  • 38