3
votes

Whenever I add enctype="multipart/form-data" to my html form, I get ForbiddenError: invalid csrf token

If I remove the enctype, it works

I'm sending the csrf code like this: input(type="hidden" name="_csrf" value= csrf_token)

My Express file:

var express = require('express');
var session = require('express-session');
var cookieParser = require('cookie-parser');
var cookieSession = require('cookie-session');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var csrf = require('csurf');


var env = process.env.NODE_ENV || 'development';

module.exports = function (app, passport) {

  // Static files middleware

  // set views path and default layout
  app.set('views', config.root + '/app/views');
  app.set('view engine', 'jade');

  // bodyParser should be above methodOverride
  app.use(bodyParser.urlencoded({
    extended: true
  }));
  app.use(bodyParser.json());
  app.use(methodOverride(function (req, res) {
    if (req.body && typeof req.body === 'object' && '_method' in req.body) {
      // look in urlencoded POST bodies and delete it
      var method = req.body._method;
      delete req.body._method;
      return method;
    }
  }));

  // cookieParser should be above session
  app.use(cookieParser());
  app.use(cookieSession({ secret: 'secret' }));

  // use passport session
  app.use(passport.initialize());
  app.use(passport.session());

  // connect flash for flash messages - should be declared after sessions

  // adds CSRF support
  if (process.env.NODE_ENV !== 'test') {
    app.use(csrf());

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

My form:

form(action="/adminfruta/criarproduto" enctype="multipart/form-data" method="post")
        input(type="hidden" name="_csrf" value= csrf_token)
        .tipo Tipo do Produto
        
        input(type="radio", value="cesta", name="type", id="cesta", checked="checked")
        label.radio(for="cesta") Cesta
        input(type="radio", value="avulso", name="type", id="avulso")
        label.radio(for="avulso") Avulso
        
        label(for="name") Nome do Produto:
        input(type="text" name="name" id="name")
        label(for="code") Código da Cesta no sistema
        input(type="text" name="code" id="code")
        label(for="inventory") Estoque:
        input(type="number" name="inventory")
        label(for="description") Descrição:
        select(multiple="multiple" name="description" id="descricao")
            for item in itens
                option(value= item.id)= item.name

        label(for="quantities") Quantidade de cada item, de acordo com a ordem da lista (não da ordem que você selecionou) separado por virgulas
        input(type="text" name="quantities" id="quantities" placeholder="Exemplo: 3,1,1,4,2,6")

        .fotos
            .half
                .choose Escolha foto 1 (tamanho 250x250 px):
                input(type="file" name="foto1")
            .half
                .choose Escolha foto 2 (tamanho 250x250 px):
                input(type="file" name="foto2")
        
        .fotos
            .half
                label(for="price") Preço(em centavos):  
                input(type="number" name="price" placeholder="R$86,00  ficaria  8600")
            .half
                label(for="discount") Desconto (opcional): 
                input(type="number" name="discount")


        .tipo Categorias:
        ul.categories
            for category in categories
                li 
                    input(type="radio" value= category.id id= category.name name="category")
                    label(for= category.name) #{category.name}
        
        .tipo Produtos Relacionados:
        .relations
            select(id="related_products" name="related_products" multiple="multiple")
                for product in products
                    option(value= product.id)= product.name


        .tipo Adicionais:
        .relations

            select(id="addons" name="addons" multiple="multiple")
                for item in itens
                    option(value= item.id) #{item.name}
    
        button.button.save(type="submit") Criar Novo

My routes file

var aws = require('aws-sdk');
var multer= require('multer');
var multerS3 = require('multer-s3');

var s3 = new aws.S3({/*params: {Bucket: 'frutacor'}*/});

// var upload = multer({ dest: 'public/img/users/' });
var upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'frutacor',
    acl: 'public-read',
    key: function (req, file, cb) {
      cb(null, Date.now().toString());
    }
  })
});

module.exports = function (app, passport) {
    app.post('/adminfruta/criarproduto', admin.verifyAdmin, upload.fields([
    {name: 'foto1', maxCount: 1},
    {name: 'foto2', maxCount: 1}]), admin.createProduct);
}

And finally the admin.js file

exports.createProduct = function(req, res){
  var product = new Product(makeProduct(req.body, req.files));
  product.save();
  return res.render('admin/index');
};

var makeProduct = function(product, photos){
  product.photos.push(photos.foto1[0].location);
  product.photos.push(photos.foto2[0].location);
  return product;
};

Also, as for dependencies:

"dependencies": {
"async": "1.3.0",
"aws-sdk": "^2.4.2",
"body-parser": "1.13.2",
"compression": "1.5.1",
"connect-flash": "0.1.1",
"connect-mongo": "0.8.1",
"cookie-parser": "1.3.5",
"cookie-session": "1.2.0",
"csurf": "1.9.0",
"easyimage": "^2.1.0",
"express": "4.13.1",
"express-session": "1.11.3",
"jade": "^1.11.0",
"method-override": "2.3.3",
"morgan": "1.6.1",
"multer": "^1.1.0",
"multer-s3": "^2.3.2",
"passport": "0.2.2",
"passport-local": "1.0.0",
"sha1": "^1.1.1",
"underscore": "^1.8.3",
"view-helpers": "0.1.5",
}

I thought the reason was multer, but then I changed multer version and stopped using it as a global middleware, and I'm still getting this error.

Note: It works if I change the action to include a "?_csrf=#{csrfToken}" after my URL, but I wanted a cleaner solution, if there's one.

2
Can you share the form code and the function code that receives the upload? I have the same things and are working properly. - danilodeveloper
@danilodeveloper, I added everything I thought could be usefull, but to keep moving, I'm using ?_csrf=#{csrfToken} at the URL until I can figure it out, but I removed it from the copy paste I posted here - iagowp
Hey @iagowp, we need to test some cases here, 1 - try to add an id in you input like this input(type="hidden" name="_csrf" id="_csrf" value= csrf_token). 2 - remove the space between the value= and csrf_token like this input(type="hidden" name="_csrf" id="_csrf" value=csrf_token) - danilodeveloper
And where do you create your routes in the express file? I'm asking this because you must call your CSRF support function AFTER express.session() but BEFORE app.router, inside of app.configure(). - danilodeveloper
@iagowp Any Solution found to this? I am facing the same problem. - anshaj

2 Answers

7
votes

make form like this

<form method="post" action="/?_csrf=<%=csrfToken%>"

and remove this

input(type="hidden" name="_csrf" value= csrf_token)
0
votes

form(action="/adminfruta/criarproduto/?_csrf=<%=csrfToken%>" enctype="multipart/form-data" method="post")

and your form must contaion this too

input(type="hidden" name="_csrf" value= csrf_token)