Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CSRF token not working when submitting form in express

i'm trying to get forms working in my express app. i have a middleware function that passes the csrf token, req.session._csrf, to res.locals.csrf_token, so the view can use it. now i'm trying to use the local variable in my view and i'm getting a forbidden error from my session middleware.

here's my form code - i'm using handlebars as my templating engine:

  <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>

i've tried referencing the csrf_token variable with and without the double curly braces and neither works. any ideas on what i am doing wrong? the Error: Forbidden happens before my route function for POSTing to /api/entries is even called. so i'm pretty sure the problem is that i'm doing something wrong with referencing the csrf token..

*edit:*in regards to the "req.session._csrf is deprecated, use req.csrfToken() instead" getting logged to the console, i did:

grep -r '_csrf' .

in my app directory. here was the output.. it doesn't look like i'm referencing it anywhere besides the view, where my hidden CSRF field is named "_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}} />

here is the entire error stack i'm getting when trying to POST to the /api/entries endpoint (i stupidly neglected to mention this before, but i'm using connect-redis for session middleware):

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)

edit 2: the error in connect-redis.js is a function trying to get the current session by the session ID and failing. don't know why this would be happening, my connect-redis setup looks correct. this is killing me

like image 319
amagumori Avatar asked Dec 10 '13 02:12

amagumori


People also ask

How does CSRF token work in node JS?

CSRF Token These tokens work by linking the user session to server-generated tokens, which the server validates upon request. The tokens are present in all forms as hidden fields. So, when the client proceeds to submit the form, it contains a validation voucher that confirms the user intended this action.

How does Csurf work?

csurf is a middleware which creates token via req. csrfToken() and this token will be generated and added to all requests and makes sure that the request comes from a legitimate client. This generated token can be added as a Cookie or via templating in meta tags or form hidden fields whichever suits your needs.

Are CSRF tokens single use?

Synchronizer Token Pattern CSRF tokens should be generated on the server-side. They can be generated once per user session or for each request.


1 Answers

EDIT: If you don't need file uploads, don't use the multipart/form-data enctype. Switching to the default enctype would allow express.csrf() to parse the _csrf token.

In order to parse forms with the multipart/form-data enctype, you need use a multipart parser in your app configuration, or handle file uploads yourself. It's recommended to avoid using the included express.bodyParser() and instead use something like busboy or formidable on the routes you're expecting file uploads, to prevent an exploit.

If you go this route, your _csrf field will no longer be caught by express.csrf() because the form body will not be parsed until after the request passes that middleware. Set your form action to '/api/entries?_csrf={{csrf_token}}' to get around this.

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();
    });
   });
});
like image 98
ofShard Avatar answered Sep 30 '22 17:09

ofShard