1

I am experiencing an issue while loading sessions with passportJS. Somehow every time a request is encountered a new session. SerializeUser function cannot find the existing session and end up in creating a new session every time. Now how do I know this?
1. The mysessions table in mongodb. For every request, two entries are being created in the table. Which look something like this.

{
    "_id" : "U_MhBL17rMVdFbuXt7Y5RGjZeHR5mP7O",
    "session" : {
        "cookie" : {
            "originalMaxAge" : 2419200000,
            "expires" : ISODate("2016-06-14T14:32:30.721Z"),
            "secure" : null,
            "httpOnly" : true,
            "domain" : null,
            "path" : "/"
        },
        "passport" : {

        }
    },
    "expires" : ISODate("2016-06-14T14:32:30.721Z")
}
{
    "_id" : "fSfITl6hGLdvny1PVZ3iJ6_dFzTmNJj3",
    "session" : {
        "cookie" : {
            "originalMaxAge" : 2419200000,
            "expires" : ISODate("2016-06-14T14:32:30.808Z"),
            "secure" : null,
            "httpOnly" : true,
            "domain" : null,
            "path" : "/"
        },
        "passport" : {
            "user" : "573b11e32147fec27aa9534e"
        }
    },
    "expires" : ISODate("2016-06-14T14:32:30.808Z")
}
  1. Deserialize User is never called.
    But I am not getting why it is not called. I have seen this and this but I think I have got all that sorted out.

Here is my environment.js file

var MongoDBStore = require('connect-mongodb-session')(session);

    var sessionStore = new MongoDBStore({
        uri: "mongodb://localhost:27017/metaiotAdmin", // Development mode
        collection: 'mysessions'
    });
    sessionStore.on('error', function(error) {
        assert.ifError(error);
        assert.ok(false);
    });
    /*session options to be given to express. It's really express keeping the sessions and not passport*/
    var sessionOpts = {
        saveUninitialized: true,
        resave: false,
        store: sessionStore,
        secret: "cat at my keyboard",
        cookie: {
            httpOnly: true,
            maxAge: 2419200000
        }
    };
    /*declaring all my global variables and dependencies*/

    app.use(cookieParser("cat at my keyboard")); // Secret should be kept in a config file and the folder should be added in gitignore 
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({
        extended: true
    }));
    app.use(bodyParser.text({
        type: "text/plain"
    }));
    app.use(session(sessionOpts));
    app.use(passport.initialize());
    app.use(passport.session());

My login.js

 passport.serializeUser(function(user, done) {
        console.log("Serialize", user.id);
        done(null, user.id);
    });

    passport.deserializeUser(function(_id, done) {
        console.log("deserializeUser");
        Users.findById(_id, function(err, user) {
            console.log(err);
            done(err, user);
        });
    });

    passport.use('local-login', new LocalStrategy({
        passReqToCallback: true
    }, function(req, username, password, done) {
        Users.findOne({
            'emailId': username
        }, function(err, user) {
            if (err)
                return done(err);
            if (!user) {
                return done(null, false, {
                    message: 'Username does not exist'
                });
            } else if (password === user.password) {
                console.log("User is verified");
                req.session.save();
                return done(null, user);

            } else
                return done(null, false, {
                    message: "Password does not match"
                });
        });
    }));

    app.post('/auth/login', passport.authenticate('local-login'), function(req, res, next) {
        res.sendStatus(200);
    });

I can't see anything terribly wrong. Help is appreciated.

Community
  • 1
  • 1
Saras Arya
  • 3,022
  • 8
  • 41
  • 71

2 Answers2

0

Well to check if you are really having an issue with your backend. Try sending a request from Postman. If it still does it means it's a backend issue and probably your config is messed somewhere. If it is not. It means Postman can send a proper request but you can't. It means the problem is in your frontend.

So I double checked and found out that I stupidly deleted two lines from my angular code.

$httpProvider.defaults.withCredentials = true;
$httpProvider.defaults.useXDomain = true;

These lines are important as they set the header which in turn run the deserializer. These two lines are automatically added for Postman but not for me. I realized this after seeing the request and response from Postman and browser.

If you have any other doubts let me know

Saras Arya
  • 3,022
  • 8
  • 41
  • 71
0

For every other lost soul out there, this is what was going on in my case and how I fixed it.

I have a React FE, served on: localhost:3000 and an Express BE, served on: localhost:5000

In Express I am using Passport with 'local' strategy and a persistent session-store (Redis). I have also configured cors, to allow sending credentials from the frontend.

app.use(cors({
   origin: 'http://localhost:3000`,
   credentials: true,
   allowedHeaders: ['Content-Type'], // this is needed for sending JSON
}));

So like everybody else here, I was having problems with Passport not deserializing users. On the frontend to successfully send back the session cookie back I did:

fetch('http://localhost:5000/private', { credentials: 'include'} );

So, I was successfully receiving the connect.sid cookie on the backend, which is used for session identification, but the user never got set. Now stupidly, because of the passport.deserialize function's description ("Registers a function used to deserialize user objects out of the session"), I thought this is also the function which queries the session-store (in my case Redis) with the correct sessionId to retrieve the UserId, and then we query our database (in my case MongoDB) for the actual user data to be set on req.user. This, however, is not the case: passport.deserialize only does the second part: finding the user in MongoDB, only if it successfully received a UserId as a parameter from the session-store from some other function.

Now why was I receiving undefined if I had the session saved in session store (Redis) and the cookie with SID was being sent back to backend?

Well here's the kicker, contrary to what the name suggests { credentials: 'include'} should not only be specified when you want to send back the credential cookies, BUT also when you first make the authentication request and are RECEIVING said cookies. So for it to work you need set credentials: include while both receiving and sending the cookies.

What actually happened: On successful authentication Express was sending back an authenticated cookie, but because my POST authentication request did not include credentials: 'include' option, the browser never set the cookie. On the next GET request to a '/private' route, because the cookie was not set, Express sent a new default cookie. That was the cookie I was seeing that was being sent back - but I was sending the incorrect cookie.

All I needed to change was adding credentials: 'include' to POST:

fetch('http://localhost:5000/login', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(body),
        credentials: 'include', // this is needed for browser to set the cookie it receives
    });

So in short, always use credentials: 'include' option (or with axios withCredentials: true) both when RECEIVING and setting cookies, as well as when SENDING them.

Source: https://stackoverflow.com/a/72614577/17239746

Zirafnik
  • 11
  • 3