Search code examples
mongodbexpresssessionexpress-session

Express-session + Passport + MongoDB - req.isAuthenticated() always return false after login


I've been searching for a solution because req.isAuthenticated() always return false even after successful login.

These are the packages that I'm using:

"express": "~4.16.1"
"express-session": "^1.17.1"
"mongoose": "^5.11.5"
"passport": "^0.4.1"
"passport-local": "^1.0.0"
"connect-mongo": "^3.2.0"

I'm using connect-mongo to store session data in MongoDB

I've referred to similar problems here like this passports-req-isauthenticated-always-returning-false-even-when-i-hardcode-done

I've tried all the possible solution, still no luck.

My passport config looks like this: I'm not sure if this is because of my serialization/deserialization

config/passport.js

const LocalStrategy = require('passport-local').Strategy;
const User = require('../schemas/UserSchema');

module.exports = function (passport) {
    // used to serialize the user for the session
    passport.serializeUser(function (user, done) {
        done(null, user._id); // I tried user.id still doesn't work since document ids in mongodb starts with "_id" ?
    });

    // used to deserialize the user
    passport.deserializeUser(function (id, done) {
        User.findById(id, function (err, user) {
            if (err) {
                return done(err);
            }

            done(err, user);
        });
    });


    passport.use(
        'local-login',
        new LocalStrategy({
            usernameField: 'email',
            passwordField: 'password',
            passReqToCallback: true
        }, async (req, email, password, done) => {
            try {
                const user = await User.findOne({ email });

                if (user) {
                    user.passwordMatch(password, function (err, match) {
                        if (err) {
                            return done();
                        }
                        if (match) {
                            return done(null, user);
                        } else {
                            return done(null, false, {
                                error: 'Incorrect credentials.'
                            });
                        }
                    });
                } else {
                    return done(null, false, { error: 'Incorrect credentials.' });
                }
            } catch (err) {
                return done(err);
            }
        })
    );
};

auth routes

authRoute.js

router.post(
    '/v1/authenticate',
    validateBody(schemas.loginSchema),
    (req, res, next) => {
        passport.authenticate('local-login', (err, user, info) => {
            if (err) {
                return next(err);
            }

            if (!user) {
                return res
                    .status(401)
                    .send(makeErrorJson({ type: INCORRECT_CREDENTIALS, status_code: 401, message: info.error }));
            } else {
                console.log('LOGIN SUCCESS, IS AUTH: ', req.isAuthenticated()) // WHY IS IT FALSE?
                const userData = sessionizeUser(user);
                return res.status(200).send(userData); // I'M ABLE TO GET DATA IN POSTMAN
            }
        })(req, res, next);
    });

router.get('/v1/check-session', (req, res) => {
    console.log('IS AUTH:', req.isAuthenticated()) // STILL FALSE
    if (req.isAuthenticated()) {
        const user = sessionizeUser(req.user);
        res.status(200).send(makeResponseJson(user));
    } else {
        res.sendStatus(404);
    }
});

app.js

const createError = require('http-errors');
const express = require('express');
require('./db/db');
const path = require('path');
const session = require('express-session');
const mongoose = require('mongoose');
const logger = require('morgan');
const cors = require('cors');
const helmet = require('helmet');
const hpp = require('hpp');
const csurf = require('csurf');
const passport = require('passport');
const MongoStore = require('connect-mongo')(session);

const app = express();
const authRouter = require('./routes/api/v1/auth');

app.disable('x-powered-by');
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(cors());
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(helmet());
app.use(hpp());

const sessionOptions = {
  key: process.env.SESSION_NAME,
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: true,
  cookie: {
    expires: 14 * 24 * 60 * 60 * 1000,
    secure: false,
  }, //14 days expiration
  store: new MongoStore({
    mongooseConnection: mongoose.connection,
    collection: 'session'
  })
};


console.log('NODE_ENV =', process.env.NODE_ENV);
if (process.env.NODE_ENV === 'production') {
  app.set('trust proxy', 1); // trust first proxy
  sessionOptions.cookie.secure = true // serve secure cookies
  sessionOptions.cookie.httpOnly = true // serve secure cookies
}

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


app.use(express.static(path.join(__dirname, 'public')));

app.use('/api', authRouter);

app.use(csurf());

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('server-error', { title: 'Server Error' });
});

require('./config/passport')(passport);

module.exports = app;

After testing login in POSTMAN, user successfully gets logged in and user session data stored in DB. But still req.isAuthenticated() returning false.


Solution

  • I found the answer by invoking req.logIn in my route after successful local login.

    The problem seemed to be the passport.deserializeUser was not being called. I've referred to this problem Passport deserializeUser not being called

    If you are using the authenticate callback when you authenticate with passport you need to log the user in manually. It will not be called for you.

    This is what I've changed on my /authenticate route.

    router.post(
        '/v1/authenticate',
        validateBody(schemas.loginSchema),
        (req, res, next) => {
            passport.authenticate('local-login', (err, user, info) => {
                if (err) {
                    return next(err);
                }
    
                if (!user) {
                    return res
                        .status(401)
                        .send(makeErrorJson({ type: INCORRECT_CREDENTIALS, status_code: 401, message: info.error }));
                } else {
                    req.logIn(user, function (err) { // I added this <-- Log user in
                        if (err) {
                            return next(err);
                        }
    
                        console.log('LOGIN SUCCESS, IS AUTH: ', req.isAuthenticated())
                        const userData = sessionizeUser(user);
                        return res.status(200).send(userData);
                    });
                }
            })(req, res, next);
        });