Search code examples
node.jsexpresscsrfpassport-local

"Invalid csrf token" using CSURF in nodejs(Express).CSRF token works fine for first request but give error for all other requests


I am using NodeJS Express and passport.js for user authentication. I have implemented csrf authentication in my login form. Csrf token works fine first time when I go to the login page but when I logout and redirect to login page I got the error "Invalid csrf token".

I had already tried passing csrf token explicitly to the view(EJS Templating Engine) using res.render({csrf: req.csrfToken()}); But it does not work.

const path = require('path');
const sequalize = require('./utils/database');
const localStrategy = require('passport-local').Strategy;
//const User = require('../models/user');
const bycrypt = require('bcryptjs');

const express = require('express');
const session = require('express-session');
const sessionStore = require('express-mysql-session')(session);
const passport = require('passport');
const bodyParser = require('body-parser');
const csrf = require('csurf');
const flash = require('connect-flash');
const User = require('./models/user');

const app = express();

var options = {
    host: 'localhost',
    port: 3306,
    user: 'root',
    password: '',
    database: 'lab'
};

var mysqlStore = new sessionStore(options);

app.set('view engine', 'ejs');
app.set('views', 'views');
app.use(flash());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({
    key: 'session_cookie_name',
    secret: 'session_cookie_secret',
    store: mysqlStore,
    resave: false,
    saveUninitialized: false
}));

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

app.use(csrf());
app.use(flash());
app.use((req, res, next) => {
    //res.locals.isAuthenticated = req.session.isLoggedIn;
    passport.serializeUser(function(user, done) {
        done(null, user);
    });

    passport.deserializeUser(function(user, done) {
        done(null, user);
    });

    passport.use(new localStrategy((username, password, done) => {
        //console.log(username);
        User.findOne({ email: username })
            .then(user => {
                if (!user) {
                    req.flash('err', 'Invalid email or password.');
                    done(null, false);
                }
                bycrypt
                    .compare(password, user.password)
                    .then(doMatch => {
                        if (doMatch) {
                            // req.session.isLoggedIn = true;
                            // req.session.user = user;
                            //console.log('Success');
                            done(null, user);
                        } else {
                            req.flash('err', 'Invalid email or password.');
                            //console.log('not logged in');
                            done(null, false);
                        }
                    })
                    .catch(err => {
                        req.flash('err', 'Something did not go well.');
                        //console.log('not logged in');
                        done(null, false);
                    });
            });
    }));
    res.locals.csrfToken = req.csrfToken();
    next();
});
app.use('/', adminRoute);
sequalize
.sync()
.then(() => {
        app.listen(3000);
    })
    .catch(err => {
        console.log(err);
    });

Code for logout route

req.logOut();
    res.render('auth/login', {
        flashError: req.flash('err')
    });

HTML Code of logout button

<form id="my_form" method="post" action="/logout">
      <button onclick="document.getElementById('my_form').submit();"><i class="fa fa-power-off" style="color:#E27D60"><span> <b> Logout</b></span></i></button>
</form>

This is the error I get when I render the login page after logout using req.logout().

ForbiddenError: invalid csrf token at csrf (K:\Node LAB\node_modules\csurf\index.js:112:19) at Layer.handle [as handle_request] (K:\Node LAB\node_modules\express\lib\router\layer.js:95:5) at trim_prefix (K:\Node LAB\node_modules\express\lib\router\index.js:317:13) at K:\Node LAB\node_modules\express\lib\router\index.js:284:7 at Function.process_params (K:\Node LAB\node_modules\express\lib\router\index.js:335:12) at next (K:\Node LAB\node_modules\express\lib\router\index.js:275:10) at SessionStrategy.strategy.pass (K:\Node LAB\node_modules\passport\lib\middleware\authenticate.js:338:9) at K:\Node LAB\node_modules\passport\lib\strategies\session.js:69:12 at pass (K:\Node LAB\node_modules\passport\lib\authenticator.js:337:31) at deserialized (K:\Node LAB\node_modules\passport\lib\authenticator.js:349:7) at K:\Node LAB\app.js:140:9 at pass (K:\Node LAB\node_modules\passport\lib\authenticator.js:357:9) at Authenticator.deserializeUser (K:\Node LAB\node_modules\passport\lib\authenticator.js:362:5) at SessionStrategy.authenticate (K:\Node LAB\node_modules\passport\lib\strategies\session.js:60:10) at attempt (K:\Node LAB\node_modules\passport\lib\middleware\authenticate.js:361:16) at authenticate (K:\Node LAB\node_modules\passport\lib\middleware\authenticate.js:362:7) at Layer.handle [as handle_request] (K:\Node LAB\node_modules\express\lib\router\layer.js:95:5) at trim_prefix (K:\Node LAB\node_modules\express\lib\router\index.js:317:13) at K:\Node LAB\node_modules\express\lib\router\index.js:284:7 at Function.process_params (K:\Node LAB\node_modules\express\lib\router\index.js:335:12) at next (K:\Node LAB\node_modules\express\lib\router\index.js:275:10) at initialize (K:\Node LAB\node_modules\passport\lib\middleware\initialize.js:53:5)


Solution

  • Finally, I figured out what was the problem. Problem was that I forget to add a hidden field of csrf token in my logout form as CSRF authentication require this field with each form.

    My logout button code before:

    <form id="my_form" method="post" action="/logout">
        <button onclick="document.getElementById('my_form').submit();"><i class="fa fa-power-off" style="color:#E27D60"><span> <b> Logout</b></span></i></button>
    </form>
    

    Now I had corrected it as:

    <form id="my_form" method="post" action="/logout">
         <input type="hidden" name="_csrf" value="<%= csrfToken %>">
         <button onclick="document.getElementById('my_form').submit();"><i class="fa fa-power-off" style="color:#E27D60"><span> <b> Logout</b></span></i></button>
    </form>
    

    In the login form, hidden csrf field was already included that's why it was working fine