node.jsexpresscsrf-token

ForbiddenError: invalid csrf token using express.js


Iv been trying to integrate using csrf and after following some guides maybe iv missed something but to me it looks ok (Maybe still iv missed something)

App.js file:

const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const session = require('express-session');
const MongoDBStore = require('connect-mongodb-session')(session);
const csrf = require('csurf');

require('dotenv').config({path: path.join(__dirname, '/.env')}); //load .env variables

const app = express();
const csrfProtection = csrf();

const tasks = new MongoDBStore({
    uri: process.env.MONGODB_URI,
    session: 'mySessions'
})

app.set('view engine', 'ejs'); //EJS
app.use(express.static(path.join(__dirname, '..', '..', 'public'))); //static folder route
app.use(session({
    secret: 'my secret',
    resave: false,
    saveUninitialized: false,
    tasks : tasks
})) // setting up a session
app.use(csrfProtection); //csrf middleware


app.use((req, res, next) => {
    res.locals.isAuth = req.session.isLoggedin; //True after logging in
    res.locals.csrfToken = req.csrfToken();
    next();
})

// Routes
const userData = require('../routes/user.js');
const listRoute = require('../routes/list.js');
const adminData = require('../routes/admin.js');
const errorController = require('../../controllers/errorController');

//Middleares
app.use(bodyParser.urlencoded({ extended: false })) //Body-Parser
app.use(express.static(path.join(__dirname, "node_modules/bootstrap/dist/"))); //bootstrap
app.use(userData);
app.use(listRoute);
app.use(adminData);
app.use(errorController.error404); //404 page


// Wait for database to connect, logging an error if there is a problem
mongoose.connect(process.env.MONGODB_URI)
      .then(() => {
        console.log('Mongoose connect: Connected');
        app.listen(process.env.PORT);
        console.log('Listening to port 3000')
      })
      .catch();

And when Im trying to log in im redirecting and getting the message "ForbiddenError: invalid csrf token"

Here is User interaction file UserController.js :

const User = require('../models/user');
const bcrypt = require('bcryptjs');
const path = require("path");
const PASSWORD_SECURITY_LEVEL = 10;

exports.getRegisterUser = (req, res, next) => {
    console.log('inside registerUser in userContorller');
    res.render('../views/register', {
        pageTitle: 'Register'
    });
}

exports.getLoginUser = (req, res, next) => {
    res.render('../views/login', {
        pageTitle: 'Login',
    })
};

//TODO:Check what went wrong when logging it just redirecting maybe something with the form action check!!!
exports.postLoginUser = (req, res, next) => {
    const username = req.body.username;
    const password = req.body.password;

    //check csrfToken valid TODO:not sure this is wokrking maybe try something else
    if(req.csrfToken() !== req.body._csrf){
        res.status(403).send('CSRF token validation failed');
        return;
    }

    User.findOne({username: username})
        .then((user) => {
            if(!user){
                //TODO: Show proper message
                console.log('User field is empty');
                res.redirect('/login');//User not found
            }
            //check if the password is correct
            bcrypt.compare(password, user.password)
                .then(passwordMatch => {
                    if(passwordMatch){
                        req.session.isLoggedin = true; //setup a session
                        req.session.user = user //save the user found in foundOne at the first .then
                        console.log('Password matched saving session and redirecting...')
                        return req.session.save((err) => {
                            if(err){
                                console.log('req.session.save error: ',err);
                            }else{
                                res.redirect('/');
                            }
                        })
                    }else{
                        //TODO: Show proper message.
                        console.log('Password is not correct redirecting back to login page.')
                        res.redirect('/login');
                    }
                })
        }).catch(err => {
            console.log('findOne error is: ', err);
    })
}

exports.postUserRegister = (req, res) => {
    const username = req.body.user;
    const password = req.body.password;

    User.findOne({username: username})
        .then((userFound) => {
            if (userFound) { //new user tried to register with the same username WAS found.
                console.log('Username is already exists!');
                res.redirect('/register');
            } else{
                return bcrypt.hash(password, PASSWORD_SECURITY_LEVEL)
                    .then(hashedPassword => {
                    const newUser = new User({
                        username: username,
                        password: hashedPassword
                    });
                    newUser.save()
                        .then(() => {
                            console.log('New user saved succesfuly')
                            res.redirect('/login');
                        })
                        .catch((err) => {
                            console.log('postUserRegister err:', err);
                            res.redirect('/register')
                        })
                });
            }
        })

        .catch(err => console.log('postUserRegister error: ', err)); //ADDED NEW LINE MIGHT CAUSE PROBLEM IN REGISTER PAGE
}

exports.postUserLogOut = (req, res) => {
    req.session.destroy((err) => {
        console.log(err)
        res.redirect('/login');
    });
}

And the login.ejs file:

<!DOCTYPE html>
<html lang="en">

<head>
    <!-- Required meta tags-->
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="Colorlib Templates">
    <meta name="author" content="Colorlib">
    <meta name="keywords" content="Colorlib Templates">

    <!-- Title Page-->
    <title><%= pageTitle %></title>

    <!-- Icons font CSS-->
    <link href="/vendor/mdi-font/css/material-design-iconic-font.min.css" rel="stylesheet" media="all">
    <link href="/vendor/font-awesome-4.7/css/font-awesome.min.css" rel="stylesheet" media="all">
    <!-- Font special for pages-->
    <link href="https://fonts.googleapis.com/css?family=Poppins:100,100i,200,200i,300,300i,400,400i,500,500i,600,600i,700,700i,800,800i,900,900i" rel="stylesheet">

    <!-- Vendor CSS-->
    <link href="/vendor/select2/select2.min.css" rel="stylesheet" media="all">
    <link href="/vendor/datepicker/daterangepicker.css" rel="stylesheet" media="all">

    <!-- Register CSS-->
    <link href="/css/register.css" rel="stylesheet" media="all">
</head>

<body>
<div class="page-wrapper bg-gra-02 p-t-130 p-b-100 font-poppins">
    <div class="wrapper wrapper--w680">
        <div class="card card-4">
            <div class="card-body">
                <h2 class="title">Log in</h2>
                <form action="/login-user" method="post">
                    <!--Username -->
                    <div class="row row-space">
                        <div class="col-2">
                            <div class="input-group">
                                <label class="label">Username</label>
                                <input class="input--style-4" type="text" name="username">
                            </div>
                        </div>
                    </div>
                    <!--Password -->
                    <div class="row row-space">
                        <div class="col-2">
                            <div class="input-group">
                                <label class="label">Password</label>
                                <input class="input--style-4" type="password" name="password">
                            </div>
                        </div>
                    </div>
                    <!-- Login button press-->
                    <form action="/login-user" method="post">
                        <div class="p-t-15">
                            <div>
                                <input type="hidden" name="_csrf" value="<%= csrfToken %>">
                                <button class="login-btn btn--radius-2 btn--blue" type="submit">Log in</button>
                            </div>
                        </div>
                    </form>
                </form>

                <!--Register Button redirect -->
                <div class="p-t-15">
                    <label class="not-registered-label">Don't have a user?</label>
                    <div>
                        <a class="login-btn btn--radius-2 btn--blue" href="./register">Register Now!</a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

</body>

</html>
<!-- end document-->

Thank you for helping any help will be aprriecited (:


Solution

  • It is likely that your <input type="hidden" name="_csrf" value="<%= csrfToken %>"> contains an empty value because from what I can see you never send the csrfToken along with your views. Try adding it as property of the optional second parameter object to the res.render method on all the views you know you will need it i.e. for form submissions like so:

    res.render('../views/register', {
       pageTitle: 'Register',
       csrfToken: req.csrfToken() //< Add this
    });
    
    //..
    
    res.render('../views/login', {
       pageTitle: 'Login',
       csrfToken: req.csrfToken() //< Add this
    })
    
    

    You also seem to have a <form> within a <form> that may be causing you some issues. This form is contained within the outer form. Nested forms goes against HTML spec:

    <form action="/login-user" method="post">
       <div class="p-t-15">
          <div>
             <input type="hidden" name="_csrf" value="<%= csrfToken %>">
             <button class="login-btn btn--radius-2 btn--blue" type="submit">Log in</button>
          </div>
       </div>
    </form>