0

I am encountering a CSRF token error when submitting a form with file uploads using multer in an Express.js application. I am using enctype="multipart/form-data" to handle the file input, but the CSRF token validation fails upon form submission. I have already added csrf() middleware in my server.js, but I am still receiving the error.

I tried implementing multer with enctype="multipart/form-data" for handling file uploads in my Express.js application. I also added the CSRF protection middleware (csrf()) for security. I expected the form to submit without errors, including the CSRF token being correctly processed. However, upon submission, I receive the error "Invalid CSRF token."

I’ve verified that the CSRF token is being correctly passed in the form (<%= csrfToken %>), but the CSRF validation is still failing.

codes

const express = require("express");
const app = express();
const dotenv = require("dotenv");
const expressLayouts = require("express-ejs-layouts");
const postRoutes = require("./routes/postRoutes");
const authRoutes = require("./routes/authRoutes");
const session = require("express-session");
const { isAuthenticated } = require("./middlewares/auth.middleware");
const pgSession = require("connect-pg-simple")(session);
const csrf = require("csurf");
const flash = require("connect-flash");
const multer = require("multer");
const path = require("path");

dotenv.config(); // Load environment variables

const port = process.env.PORT || 5000;

// Sessions
app.use(
  session({
    store: new pgSession({
      conObject: {
        host: process.env.DB_HOST,
        user: process.env.DB_USER,
        password: process.env.DB_PASS,
        database: process.env.DB_NAME,
        port: process.env.DB_PORT,
      },
      tableName: "sessions",
    }),
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: true,
  })
);

// Flash messages
app.use(flash());

// Middleware to parse form data
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

// Set up csrf protection middleware
const csrfProtection = csrf();
app.use(csrfProtection);

// Set variables in locals
app.use((req, res, next) => {
  res.locals.isAuthenticated = req.session.userId ? true : false;
  res.locals.csrfToken = req.csrfToken();
  res.locals.alerts = req.flash();
  next();
});

app.use(express.static("public")); // Serve static files

app.set("view engine", "ejs"); // Use EJS for templating
app.use(expressLayouts); // Enable layouts
app.set("layout", "layout");

// Multer setup
const storage = multer.diskStorage({
  destination: "./public/uploads/",
  filename: function (req, file, cb) {
    cb(
      null,
      file.fieldname + "-" + Date.now() + path.extname(file.originalname)
    );
  },
});

const upload = multer({
  storage: storage,
  limits: { fileSize: 10000000 },
  fileFilter: function (req, file, cb) {
    const filetypes = /jpeg|png|jpg|gif/;
    const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
    const mimetype = filetypes.test(file.mimetype);

    if (mimetype && extname) {
      return cb(null, true);
    } else {
      cb("You can only upload image files");
    }
  },
});


// Add post route with file upload
app.post("/posts/add", upload.single("imageUrl"), async (req, res) => {
 const { title, content } = req.body;
  const userId = req.session.userId;
  const imageUrl = req.file ? `/uploads/${req.file.filename}` : null;

  try {
    await Posts.create({ title, content, imageUrl, userId });
    res.redirect("/posts/me");
  } catch (err) {
    console.error(err);
    req.flash("error", "Error adding post");
    req.flash("oldTitle", title);
    req.flash("oldContent", content);
    req.flash("oldImageUrl", imageUrl || null);
    res.redirect("/posts/add");
  }
});

// Routes
app.use("/posts", isAuthenticated, postRoutes);
app.use("/auth", authRoutes);


app.listen(port, () =>
  console.log(`Server is running on http://localhost:${port}`)
);

1 Answer 1

1

csurf depends on reading the token from the request body so it can be compared with the one in the cookies.

For most of your routes, that data is populated using the two lines after the comment Middleware to parse form data.

Those middleware modules do not support multipart form data, which is why you are using multer in the first place.

This means that when the file upload route is hit, csurf rejects it before the body is parsed.

In theory, you should be able to fix this by:

  1. Reorder your routes and middleware so app.post("/posts/add", upload.sing... appears before app.use(csrfProtection); and isn't caught by your general test.
  2. Add CSRF protection back for that specific route, but only after you've parsed the form data: app.post("/posts/add", upload.single("imageUrl"), csrfProtection, async (req, res) => {

Note that your example code isn't very minimal for a [mcve] so I'm running off the documentation rather than testing this.

Off the top of my head, I think multer reads all text fields automatically so you don't need to change the upload.single(...) statement.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.