2

I am trying to implement csrf protection into my project but I can't make it work with jQuery Ajax. (It works with normal posts requests, though)

If I tamper the token using chrome dev tools before I send the form, I still see "data is being processed" text rather than invalid csrf token error.

app.js

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var csrf = require('csurf');
var bodyParser = require('body-parser');
var router = express.Router();
var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
//app.set('strict routing', true);
app.set('view options', {layout: false});

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());

var csrfProtection = csrf({ cookie: true });
var parseForm = bodyParser.urlencoded({ extended: false });

app.use(express.static(path.join(__dirname, 'public')));

app.get('/form', csrfProtection, function(req, res) {
    // pass the csrfToken to the view
    res.render('send', { csrfToken: req.csrfToken() });
});


app.post('/form', parseForm, csrfProtection, function(req, res) {
    res.send('data is being processed');
});


// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function (err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function (err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});

module.exports = app;

send.jade

html
    head
        meta(name='_csrf', content='#{csrfToken}')
    body
        form(action='/form', method='POST')
            |   Favorite color:
            input(type='text', class="favori", name='favoriteColor')
            button(type='submit') Submit
    script(src="javascripts/frontend/jquery/jquery-3.0.0-alpha1.js")
    script(src="javascripts/test.js")

test.js

$(document).ready(function () {

    $.ajaxSetup({
        headers: {'X-CSRF-Token': $('meta[name="_csrf"]').attr('content')}
    });

    $('button').click(function (e) {
        e.preventDefault();
        var text = $('.favori').val();
        alert(text);
        $.post(
            "/form",
            {
                text: text
            }, function (data) {
                console.log(data);
            });
    });
});
salep
  • 1,332
  • 9
  • 44
  • 93
  • 1
    try to send the csrf token inside the payload message, `$.post( "/form", { text: text, _csrf : $('meta[name="_csrf"]').attr('content') }, function (data) { console.log(data); });` – danilodeveloper Nov 10 '15 at 17:02
  • It works, but then I need to include csrf for each ajax request. Is there a way to achieve the same thing using ajaxSetup so I won't need to repeat the same thing? – salep Nov 10 '15 at 17:33
  • Probably no, the last time that I tried to do that, I needed to include the _csrf token inside the payload ajax message. Can I answer your question so you can accept? – danilodeveloper Nov 10 '15 at 18:47
  • Maybe you can create a Jquery plugin to automatically get the _csrf token and insert into your payload message. Only an idea... – danilodeveloper Nov 10 '15 at 18:48
  • Yes, answer it please. // Good idea, I'll look into that. – salep Nov 10 '15 at 18:55
  • Ok, I'll. I'm doing your plugin, wait a second ;) – danilodeveloper Nov 10 '15 at 19:03

3 Answers3

5

Send the CSRF token inside the payload message:

$('button').click(function (e) {
    e.preventDefault();
    var text = $('.favori').val();
    alert(text);
    $.post(
        "/form",
        {
            text: text,
            _csrf : $('meta[name="_csrf"]').attr('content')
        }, function (data) {
            console.log(data);
        });
});

To facilitate your work I think you can create a Jquery plugin to do it, something like this:

(function( $ ) {
    $.postCSRF = function(to, message, callback) {
        message._csrf = $('meta[name="_csrf"]').attr('content');
        $.post(to, message, callback);
    };
}( jQuery ));

// Usage example:
$.postCSRF('/form',{text:'hi'},function(res) {
    console.log(res);
});

Example: http://jsfiddle.net/w7h4Lkxn/

danilodeveloper
  • 3,840
  • 2
  • 36
  • 56
0

Check the header to see if its passing the tampered token in the cookie or as part of the form data. It looks like your setup is for using cookies. So changing it on the form shouldn't affect the cookie. Lemme know if that helps reveal the issue.

Ravenous
  • 485
  • 4
  • 14
  • It sends _csrf token (cookie) with each request, so it's working. But how can I "verify" it when a request is made? – salep Nov 10 '15 at 17:36
  • Check out index.js https://github.com/expressjs/csurf/blob/master/index.js you'll want to implement verify token in the route. – Ravenous Nov 10 '15 at 17:48
  • @salep when you look at the csurf index.js you'll notice csrf function has next tick which passes flow back to the route the other functions do notexecute, you'll need to call those function specifically if you want access to their usage. The bottom of index.js has csrf verifytoken. – Ravenous Nov 10 '15 at 18:01
0

your doing everything exactly right but you have to disable checking for the cookie entirely!

var csrfProtection = csurf({ cookie: false });

the author mentions it here https://github.com/expressjs/csurf/issues/52

thanks for the above code with the header post as this was crucial for validating the express session token and i have shared it with others

Michael P.
  • 77
  • 5