Passport won't stay Authenticated in chai tests

Issue

I am writing some tests for some routes that are authenticated using passport and express-session. Under normal operation the routes authenticate fine and everything works as it should.

However when testing using chai, it does not authenticate properly, causing the middleware to redirect to /login.

By using console.log(req.isAuthenticated) shows that it authenticates and adds passport:{user: [userId]} to the session. But on the next request it does not retain the this and shows false for isAuthenticated.

app.js:

//------------------------------------------------------
//                 STORAGE SITE SERVER SIDE
//------------------------------------------------------
// Matthew Haywood

var path = require("path")
var express = require("express");
var app = express();
var bodyParser = require("body-parser");
var fs = require("fs-extra");
var methodOverride = require("method-override");
var passport = require("passport");
var session = require('express-session')
var flash = require('express-flash');
var bcrypt = require('bcryptjs');
var csv = require('csv-parser');
var schedule = require('node-schedule');
const { exec } = require('child_process');
var sanitizer = require('sanitize')();
var os = require('os-utils');
var addScheduledWorkflowFromFile = require('./routes/workflow/workflowRoutes').addScheduledWorkflowFromFile;
var checkUserMiddleware = require("./middleware/index").checkUser;

global.testFolder;

//Configure the app based on whether its development or production

if (process.env.NODE_ENV === 'production') {
    testFolder = '/media/pi/ELEMENTS\ B/';
    string_len = 22;
    production_flag = true
}

if (process.env.NODE_ENV === 'development') {
    testFolder = '/Users/matthaywood/Desktop/StorageSite/StorageSite/public/'
    string_len = 59;
}

//PASSPORT SETUP
const initializePassport = require('./passport-config');
const { stdout, allowedNodeEnvironmentFlags } = require("process");
const { timeStamp } = require("console");
const { ResultWithContext } = require("express-validator/src/chain");
initializePassport(
    passport,
    email => users.find(user => user.email === email),
    id => users.find(user => user.id === id)
)

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(flash())
app.use(session({
    secret: "Once again rusty is the cutest dog",
    // resave: false,
    // saveUninitialized: false
}))
app.use(passport.initialize());
app.use(passport.session());

app.use(methodOverride("_method"));

app.use(function (req, res, next) {
    res.locals.currentUser = req.user;
    next();
});

global.users = [];
global.scheduledWorkflows = []

//LOAD USERS IN FROM JSON FILE
var loadedData = JSON.parse(fs.readFileSync("users.json"))
console.log(loadedData.Users)
users = loadedData.Users
addScheduledWorkflowFromFile()

app.use('/modules', express.static("modules"));
app.use(checkUserMiddleware, express.static(testFolder));

app.use("/", codeRoutes);
    
//----------------------------------------------------------
//                          ROUTES
//----------------------------------------------------------

app.get("/", function (req, res) {
    res.render("clusterHome.ejs");
})

//Settings Route for displaying current connected HDD
app.get("/settings", function (req, res) {
    res.render("settings.ejs");
})

//--------------------------------------------------------------------
//                  ROUTES FOR USERS AND LOGIN 
//--------------------------------------------------------------------

//Register 
app.get("/register", function (req, res) {
    res.render("register.ejs");
})

app.post("/register", function (req, res) {
    var password = req.body.password;
    // var name = sanitizer.value(req.body.name,String);
    var name = req.body.name;
    // var email = sanitizer.value(req.body.email,String);
    var email = req.body.email;
    var the_hash;

    console.log('CREATING NEW ACCOUNT')

    bcrypt.hash(password, 10, function (err, hash) {

        console.log(err)

        the_hash = hash;
        the_id = Date.now().toString();
        users.push({
            id: the_id,
            name: name,
            email: email,
            password: hash
        })
        fs.mkdirSync(testFolder + name);
        fs.mkdirSync(testFolder + name + "/" + "Workflows");
        fs.mkdirSync(testFolder + name + "/Workflows/Logs" );
        fs.mkdirSync(testFolder + name + "/" + "Code");

        var data = JSON.parse(fs.readFileSync("users.json"))

        data.Users.push({
            id: the_id,
            name: name,
            email: email,
            password: hash
        })

        data = JSON.stringify(data, null, 2)
        fs.writeFileSync("users.json", data)

        passport.authenticate('local')(req, res, function () {
            res.redirect("/");
        })

        if (err) {
            console.log(err)
            res.redirect('/register');
        } 
    })
})

app.post("/delete-account", function(req,res){
    var name = req.body.userName
    console.log("DELETING ACCOUNT")
    console.log(testFolder+name)

    var data = JSON.parse(fs.readFileSync("users.json"))
    var recordToBeDeleted;
    var index;
    let obj2 = data.Users.filter((o, i) => {
        if(o['name'] == name){
            recordToBeDeleted = o
            index = i
        }
    })

    data.Users.splice(index, 1)
    data = JSON.stringify(data, null, 2)
    fs.writeFileSync("users.json", data)

    fs.rmdirSync(testFolder + name,{recursive:true})
    res.redirect("/")
})

//----------------------------------------------------------
//                   LOGIN/LOGOUT ROUTES
//----------------------------------------------------------

app.get("/login", function (req, res) {
    res.render("login.ejs");
})

app.post("/login", passport.authenticate('local'), function (req, res) {
    // console.log(req.session)
    // console.log(req.isAuthenticated())
    // console.log(req.user)
    // console.log(res.user)
    res.redirect("/");
})

//Logout
app.delete("/logout", function (req, res) {
    req.logOut();
    res.redirect('/login')
})

//Unauthorised Route
app.get("/unauthorized", function(req, res){
    res.render("unauthorised.ejs");
})

//----------------------------------------------------------
//                  LOG ROUTES
//----------------------------------------------------------

app.get("/logs", function (req, res) {
    res.render("logs.ejs")
})

app.get("/logs/get-data", function (req, res) {
    os.cpuUsage(function (v) {
        res.status(200).send({ cpuUsage: v })
    });
})

//----------------------------------------------------------
//                  AUTO GIT PULL SCHEDULE
//----------------------------------------------------------

//Only run this job if in production to pull from github

if (process.env.NODE_ENV === 'production') {
    const job = schedule.scheduleJob('*/5 * * * *', function () {
        exec('git pull', (err, stdout, stderr) => {
            if (err) {
                console.log(err);
            } else {
                console.log("Pulling from git repository")
                console.log(stdout)
                //Restart server if git has pulled changes
                if(!stdout.includes("Already up to date.")){
                    process.exit(1)
                }
            }
        })
    })
}

function serverLog(string){
    //function used to log to a file

    
}

app.listen("8080");

module.exports = app; //Export app for testing

Tests:

var server = require('../app.js')
var chai = require('chai')
var chaihttp = require('chai-http')
let should = chai.should();
var expect = chai.expect();
var chaiFiles = require('chai-files');
var request = require('supertest');
const { session } = require('passport');
var Cookie;

chai.use(chaihttp)
// chai.use(passport.session())

//TEST GET ROUTES

const userCredentials = {
    "id": "1662139278022",
    "name": "JohnDoe",
    "email": "[email protected]",
    "password": "abcd"
  }

var authenticatedUser = request.agent(server)

beforeEach(function(done){
    authenticatedUser
    .post('/login')
    .set('Connection', 'keep-alive')
    
    .send(userCredentials)
    .end(function(err, res){
        user = userCredentials.user
        res.should.have.status(302)
        console.log(res.headers)
        done()
    })
})

describe("MAIN 1 - Test GET / route", () => {
    it('Should return index page', (done) => {
        chai.request(server)
            .get('/')
            .end((err, res)=> {
                res.should.have.status(200);
                done();
            })
    })
})

describe("MAIN 2 - Test GET /register route", () => {
    it('Should return index page', (done) => {
        chai.request(server)
            .get('/register')
            .end((err, res)=> {
                res.should.have.status(200);
                done();
            })
    })
})

describe("MAIN 3 - Test GET /login route", () => {
    it('Should return index page', (done) => {
        
        chai.request(server)
            .get('/login')
            .end((err, res)=> {
                res.should.have.status(200);
                done();
            })
    })
})

describe("MAIN 4 - Test GET /unauthorized route", () => {
    it('Should return index page', (done) => {
        chai.request(server)
            .get('/unauthorized')
            .end((err, res)=> {
                res.should.have.status(200);
                done();
            })
    })
})

//TEST POST ROUTES

describe("MAIN 5 - Test POST /register route", () => {
    it('Should register a new account and create all of the associated files with an account', (done) => {
        
        var password = 'abcd'
        var name = 'JohnDoe'
        var email = '[email protected]'
        
        chai.request(server)
            .post('/register')
            .send({
                password:password,
                name: name, 
                email: email
            })
            .end((err, res)=> {
                Cookie = res.header["set-cookie"]
                res.should.have.status(200);
                done();
            })
    })
})

describe("MAIN 6 - Test POST /login route", () => {
    it('Should register a new account and create all of the associated files with an account', (done) => {
        
        var password = 'abcd'
        var email = '[email protected]'
        
        chai.request(server)
            .post('/login')
            .send({
                password:password,
                email: email,
                // provider: 'local'
            })
            .set("Cookie", Cookie)
            .end((err, res)=> {
                res.should.have.status(200);
                done();
            })
    })
})

describe("MAIN 7 - Test POST /delete-account route", () => {

    
    it('Should delete an account and remove all of the associated files with', (done) => {
             
        chai.request(server)
            .post('/delete-account')    
            .end((err, res)=> {
                res.should.have.status(200)
                done();
            })
    })
})


Output from tests:

> [email protected] test
> mocha --exit

express-session deprecated undefined resave option; provide resave option app.js:63:9
express-session deprecated undefined saveUninitialized option; provide saveUninitialized option app.js:63:9
[
  {
    id: '1652363745447',
    name: 'newaccounta',
    email: '[email protected]',
    password: '$2a$10$.aRWTBze1sLZMd9q7m0EK.BNdM0cc/yuSm2/aUqLy06NRdIDq436.'
  }
  {
    id: '1662139278022',
    name: 'JohnDoe',
    email: '[email protected]',
    password: '$2a$10$.aOjplRKpnAEj/gWxwaBD.onWqAMRGO9QkdVuw04Np0Hp6QT8xa8u'
  }
]


  MAIN 1 - Test GET / route
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
}
false
/login
{
  'x-powered-by': 'Express',
  location: '/',
  vary: 'Accept',
  'content-type': 'text/plain; charset=utf-8',
  'content-length': '23',
  'set-cookie': [
    'connect.sid=s%3ATwbTHtqs9Lsdph-YbmKadeedPz7Qw0mG.1OW4Ypu6nd%2FhXvN6mn%2FJCCntN3jQ0%2FFHVRmlnEbUqXw; Path=/; HttpOnly'
  ],
  date: 'Sat, 03 Sep 2022 12:30:46 GMT',
  connection: 'keep-alive',
  'keep-alive': 'timeout=5'
}
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
}
false
/
(node:43749) [DEP0066] DeprecationWarning: OutgoingMessage.prototype._headers is deprecated
(Use `node --trace-deprecation ...` to show where the warning was created)
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
}
false
/login
    ✔ Should return index page

  MAIN 2 - Test GET /register route
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
  passport: { user: '1662139278022' }
}
true
/login
{
  'x-powered-by': 'Express',
  location: '/',
  vary: 'Accept',
  'content-type': 'text/plain; charset=utf-8',
  'content-length': '23',
  date: 'Sat, 03 Sep 2022 12:30:46 GMT',
  connection: 'keep-alive',
  'keep-alive': 'timeout=5'
}
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
}
false
/register
    ✔ Should return index page

  MAIN 3 - Test GET /login route
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
  passport: { user: '1662139278022' }
}
true
/login
{
  'x-powered-by': 'Express',
  location: '/',
  vary: 'Accept',
  'content-type': 'text/plain; charset=utf-8',
  'content-length': '23',
  date: 'Sat, 03 Sep 2022 12:30:46 GMT',
  connection: 'keep-alive',
  'keep-alive': 'timeout=5'
}
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
}
false
/login
    ✔ Should return index page

  MAIN 4 - Test GET /unauthorized route
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
  passport: { user: '1662139278022' }
}
true
/login
{
  'x-powered-by': 'Express',
  location: '/',
  vary: 'Accept',
  'content-type': 'text/plain; charset=utf-8',
  'content-length': '23',
  date: 'Sat, 03 Sep 2022 12:30:46 GMT',
  connection: 'keep-alive',
  'keep-alive': 'timeout=5'
}
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
}
false
/unauthorized
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
}
false
/login
    ✔ Should return index page

  MAIN 5 - Test POST /register route
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
  passport: { user: '1662139278022' }
}
true
/login
{
  'x-powered-by': 'Express',
  location: '/',
  vary: 'Accept',
  'content-type': 'text/plain; charset=utf-8',
  'content-length': '23',
  date: 'Sat, 03 Sep 2022 12:30:46 GMT',
  connection: 'keep-alive',
  'keep-alive': 'timeout=5'
}
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
}
false
/register
CREATING NEW ACCOUNT
null
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
}
false
/
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
}
false
/login
    ✔ Should register a new account and create all of the associated files with an account (143ms)

  MAIN 6 - Test POST /login route
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
  passport: { user: '1662139278022' }
}
true
/login
{
  'x-powered-by': 'Express',
  location: '/',
  vary: 'Accept',
  'content-type': 'text/plain; charset=utf-8',
  'content-length': '23',
  date: 'Sat, 03 Sep 2022 12:30:47 GMT',
  connection: 'keep-alive',
  'keep-alive': 'timeout=5'
}
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
  flash: {}
}
false
/login
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
  flash: {},
  passport: { user: '1662139278022' }
}
true
/
    ✔ Should register a new account and create all of the associated files with an account (69ms)

  MAIN 7 - Test POST /delete-account route
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
  passport: { user: '1662139278022' }
}
true
/login
{
  'x-powered-by': 'Express',
  location: '/',
  vary: 'Accept',
  'content-type': 'text/plain; charset=utf-8',
  'content-length': '23',
  date: 'Sat, 03 Sep 2022 12:30:47 GMT',
  connection: 'keep-alive',
  'keep-alive': 'timeout=5'
}
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
}
false
/delete-account
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
}
false
/login
    ✔ Should delete an account and remove all of the associated files with

  STORAGE ROUTES - Test GET /storage-home route
Middleware
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
  passport: { user: '1662139278022' }
}
true
/login
{
  'x-powered-by': 'Express',
  location: '/',
  vary: 'Accept',
  'content-type': 'text/plain; charset=utf-8',
  'content-length': '23',
  date: 'Sat, 03 Sep 2022 12:30:47 GMT',
  connection: 'keep-alive',
  'keep-alive': 'timeout=5'
}
    ✔ Should return the page for storage home


  8 passing (873ms)

Middleware:

function checkUser(req,res,next){

    //May need to refactor this as this is working but a bit hacky using req.originalURl etc
    //Also does not work for videos
    console.log("Middleware")
    console.log(req.session)
    console.log(req.isAuthenticated())
    console.log(req.url)
    

    var user = req.originalUrl.split("/")[1];
    var user = user.replace("%20", " ");
    if (req.isAuthenticated()) {
        if(req.originalUrl.includes('.') && req.originalUrl != "/style.css" && req.originalUrl != "/prism.css" && req.originalUrl != "/prism.js"){
            if(user == req.user.name){
                console.log("USER " + user + " AUTHORISED FOR " + req.body.currentPath + " DIRECTORY")
                return next()
            } else {
                res.sendStatus(403)
            }
        } else {
            return next();
        }
    } else if(req.url != "/login" && req.url != "/register" && req.url != "/register?" && req.originalUrl != "/register?" ){
        
        res.redirect('/login');
    } else if(req.url === "/login" || req.url === "/register" || req.url === "/register?" || req.originalUrl === "register"){
        
        return next()
    }
}

Solution

To follow up to this question, I realised that I wasn’t using the ‘authenticatedUser’ object in each of the tests. I have now changed to the following code and this is staying authenticated for each of the test cases.

var server = require('../app.js')
var chai = require('chai')
var chaihttp = require('chai-http')
let should = chai.should();
var expect = chai.expect();
var chaiFiles = require('chai-files');
var request = require('supertest');

chai.use(chaihttp)

const userCredentials = {
    "email": "[email protected]",
    "password": "abcd"
  }

var authenticatedUser = request.agent(server)

before(function(done){
    //Set up a test account to use for testing
    var password = 'abcd'
        var name = 'JohnDoe'
        var email = '[email protected]'
        
        chai.request(server)
            .post('/register')
            .send({
                password:password,
                name: name, 
                email: email
            })
            .end((err, res)=> {
                res.should.have.status(200);
                done();
            })
})

beforeEach(function(done){
    authenticatedUser
    .post('/login')
    .set('Connection', 'keep-alive')
    
    .send(userCredentials)
    .end(function(err, res){
        res.should.have.status(302)
        done()
    })
})

describe("MAIN 1 - Test GET / route", () => {
    it('Should return index page', (done) => {
        chai.request(server)
            .get('/')
            .end((err, res)=> {
                res.should.have.status(200);
                done();
            })
    })
})

describe("MAIN 2 - Test GET /register route", () => {
    it('Should return index page', (done) => {
        chai.request(server)
            .get('/register')
            .end((err, res)=> {
                res.should.have.status(200);
                done();
            })
    })
})

describe("MAIN 3 - Test GET /unauthorized route", () => {
    it('Should return index page', (done) => {
        chai.request(server)
            .get('/unauthorized')
            .end((err, res)=> {
                res.should.have.status(200);
                done();
            })
    })
})

describe("MAIN 4 - Test POST /login route", () => {
    it('Should login to the account created at the start of the testing', (done) => {
        
        var password = 'abcd'
        var email = '[email protected]'
        
        chai.request(server)
            .post('/login')
            .send({
                password:password,
                email: email,
                // provider: 'local'
            })
            .end((err, res)=> {
                res.should.have.status(200);
                done();
            })
    })
})

describe("MAIN 5 - Test POST /delete-account route", () => {
    it('Should delete an account and remove all of the associated files with', (done) => {
        authenticatedUser
        .post('/delete-account')
        .send({
            'userName':'JohnDoe'
        })
        .end((err, res)=> {
            res.should.have.status(302)
            done();
        })
    })
})

Answered By – Matthew Haywood

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published