Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node Express and csurf - 403 (Forbidden) invalid csrf token

Looked through and tried everything I could find on here, and elsewhere by Googling...and I'm just not able to get past this. I'm using Node, Express, EJS, and attempting to use csurf on a form, that is posted w/ jQuery ajax. No matter how I configure csurf, I get "403 (Forbidden) invalid csrf token"

I've tried configuring both globally in app.js and in the controller. Here's what I tried in app.js:

var express = require('express');
var session  = require('express-session');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var mysql = require('mysql');
var flash = require("connect-flash");
var csrf = require("csurf");

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(session({
    secret: 'somethingsecret',
    resave: true,
    saveUninitialized: true,
    httpOnly: true,
    secure: false
}));
app.use(csrf());
app.use(function (req, res, next) {
    var token = req.csrfToken();
    res.cookie('XSRF-TOKEN', token);
    res.locals.csrfToken = token;
    console.log("csrf token = " + token);
    next();
});
app.use(flash());
app.use(express.static(path.join(__dirname, 'public')));

app.use(function (err, req, res, next) {
    if (err.code !== 'EBADCSRFTOKEN') return next(err);

    // handle CSRF token errors here
    res.status(403);
    res.send('form tampered with');
})

//routing
var routes = require('./routes/index');
var users = require('./routes/users');
var register = require('./routes/register');

app.use('/', routes);
app.use('/users', users);
app.use('/register', register);

...with this controller:

var express = require("express");
var router = express.Router();
var bodyParser = require("body-parser");
var userSvc = require("../service/userservice");

var jsonParser = bodyParser.json();

router.get("/", function(req, res, next) {
    console.log("token = " + token);
    userSvc.getAllPublicRoles(function(data) {
        res.render("register", {
            title: "Register a new account",
            roles: data
        });
    });
});

router.post("/new", jsonParser, function(req, res, next) {
    userSvc.addUser(req.body, function(result) {
        console.log("New user id = " + result.insertId);
        res.send('{"success" : "Updated Successfully", "status" : 200}');
    });
});

...and this view:

form:

<form id="registerForm" class="form-horizontal" method="post">
    <input type="hidden" name="_csrf" value="<%= csrfToken %>" />

ajax call:

        $.ajax({
            url: "/register/new",
            type: "POST",
            dataType: "json",
            data: user
        }).done(function(data) {
            if (data) {
                console.log("Success! = " + data);
            }
        }).fail(function(data) {
            console.log("Something went wrong: " + data.responseText);
        });

Then I just tried just doing everything in the controller, removing all references, calls, etc. from app.js, and using the same form and ajax call as above:

var express = require("express");
var router = express.Router();
var bodyParser = require("body-parser");
var csrf = require("csurf");
var userSvc = require("../service/userservice");

var csrfProtection = csrf();
var jsonParser = bodyParser.json();

router.get("/", csrfProtection, function(req, res, next) {
    var token = req.csrfToken();
    console.log("token = " + token);
    userSvc.getAllPublicRoles(function(data) {
        res.render("register", {
            title: "Register a new account",
            csrfToken: token,
            roles: data
        });
    });
});

router.post("/new", jsonParser, csrfProtection, function(req, res, next) {
    userSvc.addUser(req.body, function(result) {
        console.log("New user id = " + result.insertId);
        res.send('{"success" : "Updated Successfully", "status" : 200}');
    });
});

Not sure where to go from here. I've been using node for about two weeks, in my spare time, so pardon my ignorance here.

like image 858
Tsar Bomba Avatar asked Nov 21 '15 22:11

Tsar Bomba


People also ask

What does it mean when it says the CSRF token is invalid?

A CSRF token is a secure random token (synchronizer token) that is used to prevent CSRF attacks. The "Invalid CSRF token, reload page to fix the problem" error means that your browser couldn't create a secure cookie, or couldn't access that cookie to authorize your login.

Does Express have CSRF protection?

Both frameworks have CSRF protection built in but don't understand each other without manual adjustments. Express is built on top of the connect framework which has a native csrf middleware. It generates a random string token that is unique for each user. The token is saved in the user's session on the server.


3 Answers

If you want to store the token in a cookie instead of the session, let csurf create the cookie for you e.g.

// Store the token in a cookie called '_csrf'
app.use(csrf({cookie: true));

// Make the token available to all views
app.use(function (req, res, next){
    res.locals._csrf = req.csrfToken();
    next();
});

Then you need to make sure the token is available when you're making the call using AJAX either via the POST'ed data, or as a custom request header such as 'xsrf-token'.

At the minute, you're providing the token to the form, but not the actual request (sent using AJAX).

For example, you could render the token in the AJAX setup:

$.ajaxSetup({
   headers: {"X-CSRF-Token": "{{csrfToken}}" }
}); 
like image 180
Ash Avatar answered Sep 30 '22 18:09

Ash


After several more hours of troubleshooting and searching, I found a post that helped answer it. All I needed was to pass the header value in the ajax post. Makes sense, I just overlooked it. Like so:

<input type="hidden" id="_csrf" name="_csrf" value="<%= csrfToken %>" />

...and then in jQuery:

    $.ajaxSetup({
        headers: {"X-CSRF-Token": $("#_csrf").val()}
    });
like image 36
Tsar Bomba Avatar answered Sep 30 '22 18:09

Tsar Bomba


An another approach over my personal project is to resend a new token when I sucessfully submit my form:

For example over my form (that does file upload) I have the follwing html:

<form id="upload_form" type="multipart/form-data" data-csrf="{{csrfToken}}" method="post" action="/data_assets">
  <input id="excell_upload" type="file" style="visible:hidden" name="data_assets"/>
</form>

And on file change I trigger the upload like that:

 $('#excell_upload').on('change',function(event){

      event.preventDefault();
      var formData = new FormData($("#upload_form")[0]);

      $.ajax({
        'type':$("#upload_form").attr('method'),
        'data': formData,
        'url': $("#upload_form").attr('action'),
        'processData': false,
        'contentType': false,
        'mimeType': 'multipart/form-data',
        'headers': {"X-CSRF-Token": $("#upload_form").attr('data-csrf') },
        'beforeSend': function (x) {
           if (x && x.overrideMimeType) {
               x.overrideMimeType("multipart/form-data");
          }
          $('#trigger_upload').addClass('disabled');
        },
        'success':function(data){
          $('#upload_form').attr('data-csrf',data.csrfToken)
        },
        'fail':function(){

        },
        'complete':function(){
          $('#trigger_upload').removeClass('disabled');
        }
      });
    });

As you notice I receive a new csrf token in order to be able to reuse my form for new submits. I regenerate the CSRF token like that:

app.post('/data_assets',function(req,res,next){
  res.json({'csrfToken':req.csrfToken()});
});
like image 41
Dimitrios Desyllas Avatar answered Sep 30 '22 18:09

Dimitrios Desyllas