13

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!

laggingreflex
  • 32,948
  • 35
  • 141
  • 196
Larissa Leite
  • 1,358
  • 3
  • 21
  • 36
  • Could you show us the code where Passport is initialized ? People often forget to initialize it, or do it in a wrong order. It should be initiliazed like this : `app.use(express.session({ secret: 'keyboard cat' })); app.use(passport.initialize()); app.use(passport.session());` Also, could you show us the code for the authentication route ? – Waldo Jeffers Sep 30 '14 at 10:56
  • Edited in the question :) – Larissa Leite Sep 30 '14 at 11:29
  • Ok, everything seems alright to me. Dumb question : you said that `passport` is empty when you log out `req.session`. Where (in the code) did you do it ? Also, you said *`passport.serializeUser` is called just fine.*, but what happens when you try to log the `user` param ? – Waldo Jeffers Sep 30 '14 at 13:09
  • I log it out in app.get('/home'), which is the route called when the authentication is successful. user is undefined – Larissa Leite Sep 30 '14 at 13:13
  • Okay, so the authentication itself is probably never successful... Another dumb question : since you're logging out in `app.get('/home')`, I am assuming you're visiting `/home` only *after* signing in (otherwise, it's normal `req.session.passport` is undefined). Could you now log out `user` and `err` at the beginning of `User.findOne`'s callback ? We'll get to the bottom of this :) – Waldo Jeffers Oct 01 '14 at 08:25
  • user is correct (it is filled in with facebook data like email and name). err is null – Larissa Leite Oct 01 '14 at 10:25
  • That's very weird. `user` should be defined in `serializeUser`. The example suggests the following code `app.get('/auth/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login' }), function(req, res) { res.redirect('/home'); });` for the authentication route. It shouldn't make any difference but could you try that ? – Waldo Jeffers Oct 01 '14 at 14:14
  • yeah, I agree, but serializeUser is only called afterwards. It didn't work with this route (didn't even call passport authentication). – Larissa Leite Oct 01 '14 at 15:15
  • @WaldoJeffers anything else that I should try/check? – Larissa Leite Oct 05 '14 at 15:48
  • @Daniel I would like to know this as well, I have the same issue. – DatBassie Dec 04 '14 at 10:16
  • No, I still haven't solved this issue :( – Larissa Leite Dec 04 '14 at 11:59

3 Answers3

8

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: 'test@wp.pl' }
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: 'test@wp.pl' } } }

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

Community
  • 1
  • 1
Daniel Kmak
  • 18,164
  • 7
  • 66
  • 89
  • You can see if there is cors issue, by peeping into js console and then overcome it in development by using --disable-web-security flag on your browser. – Ashok Kumar Sahoo Dec 04 '14 at 18:50
6

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.

Greg
  • 2,559
  • 4
  • 28
  • 46
0

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.

DatBassie
  • 313
  • 5
  • 21