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

匿名 (未验证) 提交于 2019-12-03 01:23:02

问题:

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:

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.

回答1:

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}}" } });  


回答2:

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:

...and then in jQuery:

    $.ajaxSetup({         headers: {"X-CSRF-Token": $("#_csrf").val()}     }); 


回答3:

other than adding the "X-CSRF-Token" to the header on post you want to disable cookies entirely!

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

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

cookie and session validation should not be combined -- although it is a bit misleading since he has combined cookie and session validation in his documentation: https://github.com/expressjs/csurf#simple-express-example



回答4:

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:

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()}); }); 


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!