How to implement CSRF protection in Ajax calls using express.js (looking for complete example)?
Asked Answered
M

5

23

I am trying to implement CSRF protection in an app built using node.js using the express.js framework. The app makes abundant use of Ajax post calls to the server. I understand that the connect framework provides CSRF middleware, but I am not sure how to implement it in the scope of client-side Ajax post requests.

There are bits and pieces about this in other Questions posted here in stackoverflow, but I have yet to find a reasonably complete example of how to implement it from both the client and server sides.

Does anyone have a working example they care to share on how to implement this? Most of the examples I have seen, assume you are rendering the form on the server-side and then sending it (along with the embedded csrf_token form field) to the client-side. In my app, all content is rendered on the client-side (including templates) via Backbone.js. All the server does is provide values in JSON format, which are utilized by various Models in Backbone.js on the client-side. By my understanding I would need to retrieve the csrf_token via ajax first before it can be used. However, I am concerned this may be problematic from a security standpoint. Is this a valid concern?

Monogyny answered 26/6, 2012 at 2:37 Comment(0)
F
33

It can be done by adding meta tag for CSRF token and then pass CSRF token with every Ajax request

Server

Add CSRF middleware

app.use(express.csrf());
app.use(function (req, res, next) {
  res.locals.token = req.session._csrf;
  next();
});

You can pass a CSRF token to the client side via, say, a meta tag. For ex, in Jade

meta(name="csrf-token", content="#{token}")

Client

jQuery has a feature called ajaxPrefilter, which lets you provide a callback to be invoked every Ajax request. Then set a header using ajaxPrefilter.

var CSRF_HEADER = 'X-CSRF-Token';

var setCSRFToken = function (securityToken) {
  jQuery.ajaxPrefilter(function (options, _, xhr) {
    if (!xhr.crossDomain) {
      xhr.setRequestHeader(CSRF_HEADER, securityToken);
    }
  });
};

setCSRFToken($('meta[name="csrf-token"]').attr('content'));
Falster answered 4/8, 2013 at 10:14 Comment(4)
probably the best answer herePtolemaic
I disagree. It's a good answer but it doesn't address the lack of server-side rendering. It mentions using Jade to render a meta tag on the page. But that tag's value needs to be generated server side and since the original asker says that none of his views are generated server-side this won't work.Punitive
How can we validate this token in the server side?Sharpshooter
@Bill I agree. The asker ask a solution for client-side render and the server-side only provide restful api.Dimitrovo
P
6

server.js

...
// All Cookies/Sessions/BodyParser go first
app.use(express.csrf());
...
// Get the request
app.post('/ajax', function(req, res){
    res.render('somelayout', {csrf_token: req.session._csrf});
});

In somelayout.jade

input(type='hidden', name='_csrf', value=csrf_token)

The CSRF middleware only generates the csrf token once per session, so it will probably not change for the duration of a user's visit.

Also, it doesn't check for the token on GET and HEAD requests. As long as the token is in the request (header, body, or query), you're good. That's pretty much all there is to it.

Pedalfer answered 26/6, 2012 at 3:3 Comment(2)
Thanks for the quick response. In my app, all client side content is performed via ajax. Actual rendering of content (including templates) are performed on the client-side. All the server does is provide variable data to the client-side in JSON format. This would mean that I have to retreive the CSRF token via ajax to render it into the page so that it can then be sent back in the ajax post request. I am worried that this might be problematic from a security standpoint, is this a valid concern?Monogyny
If you can, a good way to get the CSRF to the client is to render it out inside a meta tag (a la Rails) or something similar from Express.Roup
M
3

Since you are using Backbone.js for your application, I am assuming that it is a SPA and you initially load an index.html file, then make any other requests are made via ajax calls. If so, you can add a small snippet of JS code to your index.html file to hold the crsf token for any future ajax calls.

For example:

index.html (using Handlebars for templating...)

<!DOCTYPE html>
<html>
    <head>
        ...
        <script type="text/javascript">
            $( function() {
                window.Backbone.csrf = "{{csrfToken}}";
            });
        </script>
    </head>
    <body>
        ...
    </body>
</html>

When you render the index.html file, give it the csrf token that the express framework generated here: req.session._csrf

When you use Backbone.js, it sets a global variable called Backbone. All that the previous function is doing is seting a property called csrf to the global Backbone object. And when you make an ajax call to POST data, simply add the Backbone.csrf variable to the data as _csrf that is being sent via the ajax call.

Margaretamargarete answered 15/3, 2013 at 20:21 Comment(0)
R
1

In Server:

app.use(function (req, res) {
  res.locals._csrf = req.csrfToken();
  res.locals.csrf_form_html = '<input type="hidden" name="_csrf" value="' + req.csrfToken() + '" >';
  req.next();
});

In Client: (swig template)

var csrf = {{ _csrf|json|safe }};

$.ajaxSetup({
  headers: {
    'X-CSRF-Token': csrf
  }
});

$.post("/create", data, function(result) {
  console.log(result);
}).fail(function(){
  console.log(arguments);
});
Redingote answered 23/3, 2014 at 16:0 Comment(1)
How do you validate the token in the server side?Sharpshooter
D
0

1. Add csrf protection middleware:

app.use(csrf({cookie: true}));

// csrf middleware
app.use(function (req, res, next) {
   res.cookie('X-CSRF-Token', req.csrfToken());
   // this line below is for using csrfToken value in normal forms (as a hidden input)
   res.locals.csrfToken = req.csrfToken(); 
   next();
});

// routing setup goes here

2. Add a beforeSend callback using $.ajaxSetup: (add this somewhere before all your ajax calls)

$.ajaxSetup({
beforeSend: function (xhr, settings) {
    function getCookie(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }

    if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
        // Only send the token to relative URLs i.e. locally.
        xhr.setRequestHeader("X-CSRF-Token", getCookie('X-CSRF-Token'));
    }
}
});

3. That's it! now you can send ajax requests and you don't need to add anything in headers or as a request parameter to pass through csrf.

Dunt answered 6/7, 2017 at 9:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.