问题
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.
回答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:
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()});
});
回答3:
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()}
});
回答4:
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
来源:https://stackoverflow.com/questions/33849252/node-express-and-csurf-403-forbidden-invalid-csrf-token