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.