2013-12-10 111 views
5

我试图让表单在我的快速应用中工作。我有一个中间件函数,它将csrf标记req.session._csrf传递给res.locals.csrf_token,以便视图可以使用它。现在我试图在我的视图中使用本地变量,并从会话中间件中获取禁用的错误。CSRF令牌在提交表单时无法正常工作

这里是我的表单代码 - 我使用的车把我的模板引擎:

<form method='post' action='/api/entries' enctype='multipart/form-data' > 
    <input type='hidden' name='_csrf' value={{csrf_token}} /> 
    <input class='foo' type='text' /> 
    <input class='bar' type='text' /> 
    <button id='submit' type='submit'> SUBMIT 
    </form> 

我已经尝试过使用和不使用双花括号既不作品引用csrf_token变量。关于我做错什么的想法?错误:禁止发生在我的路由功能被发送到/ api /条目之前甚至被调用。所以我敢肯定,问题是,我做一些错误引用CSRF令牌..

* 编辑: *问候到“req.session._csrf已过时,使用req.csrfToken ()而不是“登录到控制台,我没有:

grep -r '_csrf' . 

在我的应用程序目录。这里是输出..它看起来并不像我除了看,在我的藏CSRF场被命名为“_csrf”随处引用它..

./node_modules/express/node_modules/connect/lib/middleware/csrf.js: var secret = req.session._csrfSecret; 
./node_modules/express/node_modules/connect/lib/middleware/csrf.js:  req.session._csrfSecret = secret; 
./node_modules/express/node_modules/connect/lib/middleware/csrf.js:  Object.defineProperty(req.session, '_csrf', { 
./node_modules/express/node_modules/connect/lib/middleware/csrf.js:   console.warn('req.session._csrf is deprecated, use req.csrfToken() instead'); 
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: return (req.body && req.body._csrf) 
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: || (req.query && req.query._csrf) 
./v/home.hbs: <input type='hidden' name='_csrf' value={{csrf_token}} /> 
./v/show.hbs: <input type='hidden' name='_csrf' value={{csrf_token}} /> 

这里是整个错误栈我是试图张贴到/ API时获取/条目端点(我愚蠢地忽视了之前提到这一点,但我使用连接,Redis的会话中间件):

Error: Forbidden 
    at Object.exports.error (appFolder/node_modules/express/node_modules/connect/lib/utils.js:63:13) 
    at createToken (appFolder/node_modules/express/node_modules/connect/lib/middleware/csrf.js:82:55) 
    at Object.handle (appFolder/node_modules/express/node_modules/connect/lib/middleware/csrf.js:48:24) 
    at next (appFolder/node_modules/express/node_modules/connect/lib/proto.js:193:15) 
    at next (appFolder/node_modules/express/node_modules/connect/lib/middleware/session.js:318:9) 
    at appFolder/node_modules/express/node_modules/connect/lib/middleware/session.js:342:9 
    at appFolder/node_modules/connect-redis/lib/connect-redis.js:101:14 
    at try_callback (appFolder/node_modules/redis/index.js:580:9) 
    at RedisClient.return_reply (appFolder/node_modules/redis/index.js:670:13) 
    at ReplyParser.<anonymous> (appFolder/node_modules/redis/index.js:312:14) 

编辑2:在连接错误-redis.js是一个试图通过会话ID获取当前会话并失败的函数。不知道为什么会发生这种情况,我的connect-redis设置看起来是正确的。这正在杀死我

+0

你是如何渲染模板?你传递的值是'csrf_token'? – WiredPrairie

+0

我有一个函数在我的快速配置,看起来像:app.use(函数(REQ,RES,下一个){res.locals.csrf_token = req.session._csrf;下一步();}) – amagumori

+0

我试着明确地添加它到res.render()中包含的本地对象,并且这些对象不起作用 – amagumori

回答

7

CSRF语法在最新版本的Express/Connect中略有变化。现在,您希望您的中间件看起来像这样:

.use(express.csrf()) 
.use(function (req, res, next) { 
    res.cookie('XSRF-TOKEN', req.csrfToken()); 
    res.locals.csrftoken = req.csrfToken(); 
    next(); 
}) 

为了测试你的代码,请注意,您首先需要获取表单页面,以便产生CSRF令牌。只有这样你的POST才能成功。如果失败,则需要在重新尝试POST之前重新加载浏览器中的页面。

+0

实现它,现在我得到一个TypeError:对象#没有方法'csrfToken'当我尝试获取页面的形式 – amagumori

+0

也我快递3.3.4 – amagumori

+0

请升级到3.4 .6并再试一次。 – dankohn

7

编辑:如果您不需要文件上传,请不要使用multipart/form-data enctype。切换到默认的enctype将允许express.csrf()解析_csrf令牌。

为了使用multipart/form-data enctype解析表单,您需要在应用配置中使用多部分分析器,或者自行处理文件上传。建议避免使用包含的express.bodyParser(),而应在预计要上传文件的路由上使用busboyformidable之类的内容,以防止出现exploit

如果你走这条路线,你的_csrf字段将不再被express.csrf()所捕获,因为直到请求通过该中间件之后,表单主体才会被解析。将表单操作设置为'/api/entries?_csrf={{csrf_token}}'以解决此问题。

var fs = require('fs'); 
var async = require('async'); 
var express = require('express'); 
var formidable = require('formidable'); 
var app = express(); 

app.use(express.urlencoded()) 
    .use(express.json()) 
    .use(express.cookieParser()) 
    .use(express.session()) 
    .use(express.csrf()) 

app.get('/upload', function(req, res) { 
    // File uploads ignored. 
    res.render('upload', {_csrf:req.csrfToken()}); 
}); 

app.post('/upload', function(req, res) { 
    // Explicitly handle uploads 
    var form = new formidable.IncomingForm(); 
    form.uploadDir = 'temp'; 

    var count = 0; 
    var maxAllowed = 10; 

    form.onPart = function(part) { 
    if (!part.filename) return form.handlePart(part); 

    count++; 

    // Ignore any more files. 
    if (count > maxAllowed) return part.resume(); 

    form.handlePart(part); 
    }; 

    form.parse(req, function(err, fields, files) { 
    // Process the files. If you don't need them, delete them. 
    // Note that you should still reap your temp directory on occasion. 

    async.map(Object.keys(files), function(key, cb) { 
     fs.unlink(files[key].path, cb); 
    }, function(err) { 
     res.end(); 
    }); 
    }); 
}); 
+1

将表单动作设置为书面形式是为我解决了问题。但是,在极少数情况下,我发现我仍然遇到了禁止的错误。经过一番调查后,我意识到我必须在表单操作中对encodeURIComponent的csrf值进行编码。 – Jerome

+0

这确实是我+1的正确答案。 –

3

我今天也打这个问题,它采取几个小时,我找到一个解决方案。希望这个答案可以帮助有我的确切问题的人。作为@amagumori,我使用redis进行会话处理并表达3.4.8,connect-redis 1.4.7。

基本上我能够确定我的快速配置的顺序会影响新令牌发出的次数。似乎所有被公开的东西都在创造一个新的标志。

具体来说,在我来说,我已经如预期的会话移动上述

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

和令牌发放呼叫

app.use(express.methodOverride()); 
app.use(express.bodyParser()); 
app.use(express.static(__dirname + '/public')); 

+0

谢谢!我用repo hach​​athon-starter遇到了同样的问题。 – JohnSz