Home » Nodejs » req.session.passport is empty, deserializeUser not called – ExpressJS, Passport

req.session.passport is empty, deserializeUser not called – ExpressJS, Passport

Posted by: admin November 30, 2017 Leave a comment

Questions:

I am having problems using sessions in Passport/ExpressJS.

When I log req.session, I can see that passport is {}:

{ cookie: 
 { path: '/',
  _expires: Mon Sep 29 2014 19:37:16 GMT-0300 (BRT),
  originalMaxAge: 3594522,
  httpOnly: true,
  secure: true },
passport: {} }

Also, I am authenticating with Facebook, and passport.deserializeUser is not being called.
This is my code:

    passport.use(new FacebookStrategy({

    // pull in our app id and secret from our auth.js file
    clientID        : configAuth.facebookAuth.clientID,
    clientSecret    : configAuth.facebookAuth.clientSecret,
    callbackURL     : configAuth.facebookAuth.callbackURL

},

// facebook will send back the token and profile
function(token, refreshToken, profile, done) {

    // asynchronous
    process.nextTick(function() {

        console.log("profile " + profile.id);
        console.log("profile.name "+profile.name.givenName)

        // find the user in the database based on their facebook id
        User.findOne({ 'facebook.id' : profile.id }, function(err, user) {

            // if there is an error, stop everything and return that
            // ie an error connecting to the database
            if (err)
                return done(err);

            // if the user is found, then log them in
            if (user) {
                //returning undefined
                //console.log(user.name + " " + user.email);
                return done(null, user); // user found, return that user
            } else {
                // if there is no user found with that facebook id, create them
                var newUser = new User();

                // set all of the facebook information in our user model
                newUser.facebook.id    = profile.id; // set the users facebook id
                newUser.facebook.token = token; // we will save the token that facebook provides to the user
                newUser.facebook.name  = profile.name.givenName + ' ' + profile.name.familyName; // look at the passport user profile to see how names are returned
                newUser.facebook.email = profile.emails[0].value; // facebook can return multiple emails so we'll take the first

                // save our user to the database
                newUser.save(function(err) {
                    if (err)
                        throw err;

                    // if successful, return the new user
                    return done(null, newUser);
                });
            }

        });
    });

}));

// used to serialize the user for the session
passport.serializeUser(function(user, done) {
    console.log("serialize");
    done(null, user.id);
});

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

I’ve tried moving both methods to the beginning but it didn’t make any difference. passport.serializeUser is called just fine.

This is where I initiliaze passport:

app.use (cookieParser());
app.use(session({ secret: 'appsecret', saveUninitialized: true, cookie: { secure: true, maxAge: new Date(Date.now() + 3600000) }, key:'connect.sid' }));

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

And this is my route:

// =====================================
// FACEBOOK ROUTES =====================
// =====================================
// route for facebook authentication and login

app.get('/auth/facebook', passport.authenticate('facebook', { scope : 'email' }));

// handle the callback after facebook has authenticated the user
app.get('/auth/facebook/callback',
    passport.authenticate('facebook', {
        successRedirect : '/home',
        failureRedirect : '/login'
    }));

Can someone help me with this?

Thanks!

Answers:

Background

For me the issue with deserializeUser not being called and empty passport object was cross-domain issue. When I was sending ajax request from localhost:4200 to localhost:1000 cookies and sessions didn’t work for my Ember.js client. However, Postman extension received correct response. Also passport object was filled with data correctly. User.deserializeUser was getting called correctly.

Solution

To solve this I had to set correct server-side and client HTTP headers.
Server-side(node.js + express + passport):

# The important part. Must go AFTER the express session is initialized
app.use passport.initialize()
app.use passport.session()

# Enable CORS
# X-Requested-With, X-AUTHENTICATION, X-IP
# https://stackoverflow.com/a/18290875/2166409
app.use (req, res, next) ->
  res.header 'Access-Control-Allow-Origin', 'http://localhost:4200'
  res.header 'Access-Control-Allow-Headers', 'Origin, X-Requested-With, X-AUTHENTICATION, X-IP, Content-Type, Accept'
  res.header 'Access-Control-Allow-Credentials', true
  res.header 'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'
  next()

app.use '/api/v1', router

Client-side:

If using jQuery add this to $.ajax options:

xhrFields: { withCredentials:true }

Then everything works as expected:

DESERIALIZE USER
{ id: '547fabf22a098ade53e6e48e',
  name: 'test',
  firstName: 'test',
  email: '[email protected]' }
LOGIN SUCCESS SESSION
{ cookie: 
   { path: '/',
     _expires: Thu Dec 04 2014 20:26:52 GMT+0100 (CET),
     originalMaxAge: 3597205,
     httpOnly: false },
  passport: 
   { user: 
      { id: '547fabf22a098ade53e6e48e',
        name: 'test',
        firstName: 'test',
        email: '[email protected]' } } }

These answers helped me: about Ember.js, jQuery.ajax.

Questions:
Answers:

For me, the root cause was the cookie security value in the express-session.

app.use(require('express-session')({
    secret: 'crackalackin',
    resave: true,
    saveUninitialized: true,
    cookie : { secure : false, maxAge : (4 * 60 * 60 * 1000) }, // 4 hours
}));

On my local machine, I wasn’t running over HTTPs so when cookie.secure is true, the session and user object were empty. When in dev mode and secure was false, it worked correctly.

Questions:
Answers:

My solution was to be consistent in linking the different pages of my site.

Prefixing the URL of a link with ‘www.’ while authenticating where there is no ‘www.’ will start a new session when linking to that prefixed page.
You can check this with a cookie viewer, see if there’s multiple cookies for each of the two options.