Passport not running deserialize

I have been banging my head against the wall for the past couple of days trying to fix this issue. Basically, I added passport to my React app for user authentication, and I want to be able to check if the user has been added to the session from a couple of components to ensure the user is logged in.

When I post the email / password to the ‘/login’ route it serializes the user, but when I redirect to the home page the user is no longer in the session. It appears that the deserialize function isn’t running at all.

Below is the passport setup and my user routes:

passport.js

const mongoose = require('mongoose');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;

const Users = require('../models/Users');

passport.serializeUser((user, done) => {
    console.log('Serialized user');

    done(null, {_id: user._id});
})

passport.deserializeUser((id, done) => {
    console.log("Deserializing user...");
    Users.findOne(
        {_id: id},
        (err, user) => {
            console.log("Deserialized user ");

            done(null, user);
        }
    )
})

passport.use( new LocalStrategy(
    {
        usernameField: 'user[email]',
        passwordField: 'user[password]'
    },
    function(email, password, done){
        Users.findOne({email: email}, (err, user) => {
            if(err) {
                done(err);
            }
            if(!user) {
                return done(null, false, { message: "Incorrect email"})
            }
            if(!user.validatePassword(password)){
                return done(null, false, { message: "Invalid password" });
            }
            return done(null, user);
        })
    }
));

module.exports = passport;

user.js (user routes)

const mongoose = require('mongoose');

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

const userRouter = require('express').Router();

const auth = require('./auth');

const Users = require('../../models/Users');

userRouter.post('/', auth.optional, function(req, res){

    const user = req.body.user;

    if(!user.email) {

        return res.status(422).json({

            errors: {

                email: 'is required'

            }

        })

    }

    if(!user.password) {

        return res.status(422).json({

            errors: {

                password: 'is required'

            }

        })

    }

    const finalUser = new Users(user);

    finalUser.setPassword(user.password);

    

    return finalUser.save()

        .then(() => res.json({ user: finalUser.toAuthJSON() }));

})

userRouter.post('/login', auth.optional, (req, res, next) => {

        const {body: {user}} = req;

        if(!user.email) {

            return res.status(422).json({

                errors: {

                    email: 'is required'

                }

            })

        }

        

        if(!user.password) {

            return res.status(422).json({

                errors: {

                    password: 'is required'

                }

            })

        }

        next();

    },

    passport.authenticate('local'), (req, res) => {

        if(req.user) {

            const user = req.user;

            user.token = user.generateJWT();

            return res.status(200).json({user: {

                id: user._id,

                email: user.email,

                token: user.token

            }});

        }

        return res.sendStatus(404);

    }

);

userRouter.get('/current', auth.required, (req, res, next) => {

    const { payload: {id}} = req;

    return Users.findById(id)

        .then((user) => {

            if(!user) {

                return res.sendStatus(400);

            }

            console.log(req.session);

            return res.json({ user: user.toAuthJSON() });

        })

});

userRouter.get('/checkLogin', auth.optional, (req, res, next) => {

    console.log(req.session)

    if(req.session.passport){

        const user = req.session.passport.user;

        return res.json(user);

    }

    return res.sendStatus(404);

})

module.exports = userRouter;

This is the code to setup my express server:

require('dotenv').config()

var createError = require('http-errors');

var express = require('express');

var path = require('path');

var cookieParser = require('cookie-parser');

var logger = require('morgan');

var cors = require('cors');

var mongoose = require('mongoose');

var session = require('express-session');

var bodyParser = require('body-parser');

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

var indexRouter = require('./routes/index');

var newRouter = require('./routes/new');

var articleRouter = require('./routes/article');

var deleteRouter = require('./routes/delete');

var userRouter = require('./routes/api/users');

var app = express();

const PORT = process.env.PORT || 4001;

mongoose.promise = global.Promise;

//connect to database

mongoose.connect(process.env.MONGO_URI, {

  useNewUrlParser: true,

  useUnifiedTopology: true

}).then(() => {

  console.log('Database sucessfully connected')

},

  error => {

      console.log('Database could not be connected: ' + error)

  }

)

// view engine setup

app.set('views', path.join(__dirname, 'views'));

app.set('view engine', 'jade');

app.use(logger('dev'));

app.use(express.json());

app.use(express.urlencoded({ extended: false }));

app.use(cookieParser('coder-session'));

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

app.use(cors());

app.use(bodyParser.json());

app.use(bodyParser.urlencoded({extended: false}));

app.use(session({ 

  secret: 'coder-session', 

  resave: false, 

  saveUninitialized: false 

}));

app.use(passport.initialize());

app.use(passport.session());

app.use('/', indexRouter);

app.use('/new', newRouter);

app.use('/article', articleRouter);

app.use('/delete', deleteRouter);

app.use('/users', userRouter);

// 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('error');

});

app.listen(PORT, () => {

  console.log("Listening on port " + PORT);

})

module.exports = app;

Also my code forthe login form:

import React from 'react';

import '../../resources/css/LoginForm.css';

import {Redirect} from 'react-router-dom';

export class LoginForm extends React.Component {

    constructor(props) {

        super(props);

        this.state = {

            user: {},

            redirect: false,

            returnedUser: {}

        }

        this.handleChange = this.handleChange.bind(this);

        this.handleSubmit = this.handleSubmit.bind(this);

    }

    handleChange(e) {

        const user = this.state.user;

        user[e.target.name] = e.target.value;

        this.setState({

            user: user

        })

    }

    handleSubmit(e) {

        e.preventDefault();

        const user = {

            user: this.state.user

        }

        fetch("http://localhost:4001/users/login", {

            method: 'post',

            body: JSON.stringify(user),

            headers: {

                "Content-Type": "application/json"

            }

        })

        .then(response => response.json())

        .then(jsonResponse => {

            if(jsonResponse.errors){

                const error = "*" + Object.keys(jsonResponse.errors)[0] + " " + Object.values(jsonResponse.errors)[0];

                document.getElementById("error-message").innerText = error;

                document.getElementById(Object.keys(jsonResponse.errors)[0]).style.border = 'red';

            }

            else {

                console.log(jsonResponse);

                this.setState({

                    redirect: true,

                    returnedUser: jsonResponse.user

                })

            }

        });

    }

    render() {

        if(this.state.redirect){

            return (<Redirect to={{

                pathname: "/",

                state: { loggedIn: true, user: this.state.returnedUser }

              }} />)

        }

        else {

            return (

                <div className="Form">

                    <form id="login-form" onSubmit={this.handleSubmit}>

                        <div className="input-field">

                            <label htmlFor="email">Email: </label>

                            <input id="email" name="email" type="text" value={this.state.email} onChange={this.handleChange}/>

                        </div>

                        <div className="input-field">

                            <label htmlFor="password">Password: </label>

                            <input id="password" name="password" type="password" value={this.state.password} 

                                onChange={this.handleChange}/>

                        </div>

                        <div id="error-message"></div>

                        <input type="submit" value="Login" className="button" />

                    </form>

                </div>

            );

        }

    }

    

}

Welcome, rjgallego.

Would you mind posting a link to your project? Maybe a GitHub repo? Ideally, if you could share it in a CodeSandbox/Repl.it/Glitch, then I would be more than happy to go over it.

I had to make the repository public, but below is the link to the project on my github. Thank you!

1 Like

I am not sure if this will persist the session, but if you change the serialliser from:

done(null, {_id: user._id});

To:

done(null, user._id);

Does anything change on your side?

Actually, it looks like the issue could be here:

passport.authenticate('local'), (req, res) => {
        if(req.user) {
            const user = req.user;
            user.token = user.generateJWT();

            return res.status(200).json({user: {
                id: user._id,
                email: user.email,
                token: user.token
            }});
        }

        return res.sendStatus(404);
    }

I would expect the redirect to occur here, and the deserialisation to occur after.

As an aside, I notice the id here is not an ObjectId which I would think will throw an error:

passport.deserializeUser((id, done) => {
    console.log("Deserializing user...");
    Users.findOne(
        {_id: id},
        (err, user) => {
            console.log("Deserialized user ");

            done(null, user);
        }
    )
})

Hope that makes sense.

It makes sense, I tried replacing the JSON I was returning with a res.redirect to the root path, it still isn’t calling the deserialize function.

passport.authenticate('local'), (req, res) => {
        if(req.user) {
            const user = req.user;
            user.token = user.generateJWT();

            console.log(req.session);
            return res.redirect('/');
        }
        return res.sendStatus(404);
    }

Below is the console output, I have it print the session after sending the user email/password to the /login path and also after calling the root path. The user is added to the session after the post but not after the redirect to the root path:

[nodemon] restarting due to changes...
[nodemon] starting `node ./bin/www`
Listening on port 4001
Database sucessfully connected
OPTIONS /users/login 204 3.542 ms - 0
Serialized user
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
  passport: { user: { _id: 5f9c5872f35db424b4b72a20 } }
}
POST /users/login 302 242.345 ms - 23
OPTIONS / 204 0.297 ms - 0
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
}
GET / 304 99.740 ms - -

I am sorry, but I cannot see anything else which might help :confused:

If something comes to me, I will be sure to let you know, but otherwise I hope you find out how to resolve this.

I was actually able to fix this yesterday. The login was running correctly when I used Postman but not in the browser which means I had an issue with sending cookies from a different origin than the server.

For fetch functions, I had to add credentials: "include" and 'Accept': 'application/json', 'Access-Control-Allow-Origin': 'http://localhost:3000/' to the fetch headers to allow the request to send the cookie to the server. I also had to add res.setHeader('Access-Control-Allow-Credentials', 'true') to the route so it would accept the credentials from the client. Now it will deserialize the user when I re-route to the home page. Below is the full code for the fetch and passport authorization:

fetch to login user:

fetch("http://localhost:4001/users/login", {
            method: 'post',
            credentials: "include",
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
                'Access-Control-Allow-Origin': 'http://localhost:3000/'
            },
            body: JSON.stringify(user)
        })
        .then(response => response.json())
        .then(jsonResponse => {
            if(jsonResponse.errors){
                const error = "*" + Object.keys(jsonResponse.errors)[0] + " " + Object.values(jsonResponse.errors)[0];
                document.getElementById("error-message").innerText = error;
                document.getElementById(Object.keys(jsonResponse.errors)[0]).style.border = 'red';
            }
            else {
                this.setState({
                    redirect: true,
                    returnedUser: jsonResponse.user
                })
            }
        });

user authentication

passport.authenticate('local'), (req, res) => {
        if(req.user) {
            res.setHeader('Access-Control-Allow-Credentials', 'true')
            const user = req.user;
            user.token = user.generateJWT();


            return res.status(200).json({user: {
                id: user._id,
                email: user.email,
                token: user.token
            }});
        }
        return res.sendStatus(404);
    }
1 Like