[Fixed] Node Js – Express – CSURF : "invalid csrf token"

Issue

I’m trying to implement csurf within an express app hosted in firebase and I’m facing the ‘EBADCSRFTOKEN’ error in one specific case.
This error only occurs when a user is logged in through a session cookie.

I’m sending the data of my contact form with JQUERY.
It works perfectly when no user is logged in.

Route that display the contact form

router.get('/contact', async(req, res, next) => { 
  try {
    let user = await RequestHelper.checkSessionCookie(req);
    let data = {
      csrfToken: req.csrfToken()
    };
    if (user) { 
      data.customer = await UserController.getUser(user.uid);
    }
    return res.render('contact', data);
  } catch(error) {
    next(error);
  }
});

Ajax request in the contact form

let csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');    
let data = {...}
$.ajax({
  type: "post",
  data: data,
  headers: {
      'csrf-token': csrfToken
  }, 
  url: "/contactForm",
  success: function(code) {
    window.location.href = '/contacted';
  },error:  function (error) {
    console.log(error);
  }
});

Route that process the form data

Since you don’t have to be logged in to access this route I don’t call session middleware.

router.post('/contactForm', async(req, res) => {
  console.log("CSRF OK !");
});

Server code

const functions             = require('firebase-functions');
const admin                 = require('firebase-admin');
const path                  = require('path');
const favicon               = require('express-favicon');
const express               = require('express');
const cookieParser          = require('cookie-parser')();
const csurf                 = require('csurf');
const cors                  = require('cors')({origin: true});

admin.initializeApp();

const app                   = express();

app.set('view engine', 'pug');
app.set('views', config.template.directory);

app.use(favicon(path.join(__dirname, '../public', 'favicon.ico')));
app.use(cors);

app.use('/',              require('./routes/webhooks'));

app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(cookieParser);
app.use(csurf({cookie: { httpOnly: true }}));

app.use('/',              require('./routes/routes'));
app.use('/my',            require('./routes/user'));
app.use('/courses',       require('./routes/courses'));

//CSRF ERROR
app.use((error, req, res, next) => {
  if (error.code === 'EBADCSRFTOKEN') {
    console.log(error);
    console.log(req.get("csrf-token")); // <- displays the token perfectly 
    return res.status(403).send();
  } 
  return next(error);
});

The route where the session cookie is created

router.post('/sessionLogin', (req, res, next) => {
  const idToken = req.body.idToken;
  const expiresIn = 60 * 60 * 24 * 5 * 1000;
  
  return admin.auth().createSessionCookie(idToken, { expiresIn }).then((sessionCookie) => {
    const options = { maxAge: expiresIn, httpOnly: true, secure: true };
    res.cookie('__session', sessionCookie, options);
    let response = { 
      status: 'success'
    }
    return res.end(JSON.stringify(response));
  },(error) => {
    console.log(error);
    return res.status(401).send();
  });  
});

Even with my own custom function to check the token the problem is the same

app.use(csurf({ cookie: true, value: (req) => {
  console.log(" ------- CHECK CSRF TOKEN ------- ");
  console.log(req.get("csrf-token"));
  return req.get("csrf-token");
}}));

>   ------- CHECK CSRF TOKEN ------- 
>  6wXhahio-AN2f3zlHJPNShNv1KUAB6tOWN3U
>  ForbiddenError: invalid csrf token

Thank you for your help.

Solution

I tried to set same cookie name that I’m using to store my session with firebase and it seems to work.

app.use(csurf({ cookie: { key: "__session", httpOnly: true }));

From the firebase doc :

When using Firebase Hosting together with Cloud Functions or Cloud Run, cookies are generally stripped from incoming requests. This is necessary to allow for efficient CDN cache behavior. Only the specially-named __session cookie is permitted to pass through to the execution of your app.

Leave a Reply

(*) Required, Your email will not be published