19

Do I implement Serialize and Deserialize?

RedisStore is setup as my session store with Express. Does this mean that I DO NOT implement Serialize and Deserialize? Will it happen automatically?

When I don't implement these methods I get the following Express error - 500 Error: failed to serialize user into session. When I do implement them I'm not sure what to put in the Deserialize.

The code below appears to work but the sessions are not persisting. I need to login everytime I visit the site.

Is there a good example anywhere of NodeJS + Passport + RedisStore?

var sessionStore = new RedisStore({
                                        host: rtg.hostname,
                                        port: rtg.port,
                                        db: redisAuth[0],
                                        pass: redisAuth[1]
                                      });

passport.use(new ForceDotComStrategy({
    clientID: clientId,
    clientSecret: clientSecret,
    callbackURL: myurl
},
function(token, tokenSecret, profile, done) {
    console.log(profile);
    return done(null, profile);
  }
));

appSecure.configure('production', function(){
appSecure.use(allowCrossDomain);
appSecure.use(express.cookieParser(expressSecret));
appSecure.use(express.bodyParser());
appSecure.use(express.methodOverride());
appSecure.set('port', port); 
appSecure.use(express.session({ secret: expressSecret, store: sessionStore, key:'expressSid', cookie: { maxAge : 604800, domain:'.domain.com'}})); 
appSecure.use(passport.initialize());
appSecure.use(passport.session());
appSecure.use(appSecure.router);
appSecure.use(express.static(__dirname + '/public'));
appSecure.use(express.errorHandler());
});

passport.serializeUser(function( user, done ) {
    done( null, user.id);
});

passport.deserializeUser(function( user, done ) {
    done( null, user );
});
A.B
  • 20,110
  • 3
  • 37
  • 71
wisemanIV
  • 654
  • 3
  • 8
  • 21

3 Answers3

47

If you are using sessions you have to provide passport with a serialize and deserialize function. Implementing Redis as a session store has nothing to do with how passport was implement, it only deals with where the session data is stored.

Implementing Sessions with passport

As I said, the serialize and deserialize functions must be provided to passport for sessions to work.

The purpose of the serialize function is to return sufficient identifying information to recover the user account on any subsequent requests. Specifically the the second parameter of the done() method is the information serialized into the session data.

The deserialize function that you provide is intended to return the user profile based on the identifying information that was serialized to the session.

Here is the example from the Passport Guide in the section discussing sessions:

passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

In the above example passport.serializeUser() is provided a function that takes two parameters, the user profile (user) and a callback function (done). The callback function takes as it's second parameter the identifying information (user.id, but if you're using mongoDB this may be user._id) required to recover the account from the database. This will be called on every authenticated request and stores the identifying information in the session data (whether that is in a cookie or your Redis store).

passport.deserializeUser() is provided a function that also takes two parameters, the identifying information (id) and again a callback function (done). The identifying information is what was serialized to the session data in the previous request (user.id). The callback function here requires the user profile as it's second parameter, or any error in raised in retrieving the profile as it's first parameter. The User.findById() function is a lookup function for the user profile in the database. In this example User object is an instance of a mongoose model which has the findById() function.

The function provided to passport.deserializeUser() is called by the passport middleware, passport.session() prior to the route handling to store the user profile (user) to req.user.

Implementing Redis as a Session Store

The purpose of using Redis is to store session data server side so the only data stored client side is the session id. Again, this is independant of how you have implemented passport, passport doesn't care where the session data is being stored as long as you have added session support to your app. This previos question on stackoverflow addresses how to implement Redis

Community
  • 1
  • 1
Weston
  • 2,732
  • 1
  • 28
  • 34
  • I think my problem is that I don't know what the equivalent deserializeUser for RedisStore. The session storage has been abstracted away from me but now I need to know how it works to write this function. The example in your link also does't show an example of the deserialize function. – wisemanIV Oct 10 '13 at 02:54
  • If you're storing the user profiles in Redis (in addition to the session data which is stored seperately) you aren't deserializing from RedisStore you're just retrieving the user profile/data by the key. [Redis + Node.js - how do I retrieve the values](http://stackoverflow.com/questions/17251493/redis-node-js-how-do-i-retrieve-the-values) – Weston Oct 10 '13 at 20:36
  • 1
    Sorry, I just don't get it. I'm not explicitly storing anything in Redis. I'm just setting the middleware to use RedisStore. I see keys that look like sess:3343434 in Redis which I assume are serialized values for the id created by Express? At least I did until I edited the deserialize function. The login is working now but the session is not being persisted. – wisemanIV Oct 11 '13 at 03:07
  • Your last comment makes me think again that I don't need to override deserialize if there is not user profile or if I handle that separately. – wisemanIV Oct 11 '13 at 03:10
  • Hi @Weston, if I have a User as subdocument of a ServerModel, how can I implement deserializeUser function, if I need the serverId and the userId to find the user? How can I pass more than one parameter to deserializeUser function?. Thanks! – Ragnar Jun 11 '14 at 14:16
  • @Ragnar check out the section "Remembering the User in the Session" [over here](http://justjs.com/posts/are-these-the-droids-we-re-looking-for-finding-out-with-gmail-authentication). Basically you can store whatever you want for the seesion, so you could easily store two values in a JSON array. – Weston Jun 15 '14 at 21:53
  • How does Passport know what "User" is? Where would you define that? If it's a model, how can it be done in SQL? Thanks! – Adam Nov 19 '14 at 17:22
30

Bit late but i have made this visual thing to understand

  1. When and how is is an strategy/local/Facebook/etc called and how it gets to req.login or passport.serializeUser() and whats with done()?

passport.authenticate() invokes the respective strategy you provide as an argument, there you match req.body.password and req.body.username with the database stored or in memory stored password and username. if user found you pass it to done() as second argument else you return false

The done callback return back to passport.authenticate(). if done is called previously with user (ie done(null,user); ) than req,logIn() is called automatically or by user behind the scene

req.logIn() calls passport.serializeUser()

  1. Whats passport.serializeUser and Where does user.some_key go after this function has been called?

the key of user object you provide in second argument of the done in serialize function is saved in session and is used to retrieve the whole object via deserialize function.

Serialize function determine what data from the user object should be stored in the session. The result of the serializeUser method is attached to the session as req.session.passport.user = {} here for instance it would be(as we provide id as key) req.session.passport.user = {id:'xyz'}

  1. What is passport.deserializeUser and where does it fit in the workflow?

In deserialize function you provide in first argument of deserialize function that same key of user object that was given to done function in serialize call. so your whole object is retrieved with help of that key. that key here is id(key can be any key of the user object ie name,email etc) In deSerialize function that key is matched with in memory array / database or any data resource

The fetched object is attached to request object as req.user

id key can be any key of the user object ie name,email etc

Visual Flow

passport.authenticate()-----------
                                 |  
                                 |  invokes 
                                \./
       passport.use(new LocalStrategy(
            function(username, password, done) {

           // match req.body.username and req.body.password from any 
              //data base or in memory array
               if(user_is_found_and_pass_match)
                  done(null,user);--
               else                   | *1-user passed
                                      |
                  done(null,false);---| *2-user not passed
       });                            | 
                                      |return back to
passport.authenticate() <------------ |
                      |
                      |----- if user is passed in done() (*1) ,   
                            |
    req.login()   <--------- 
              |
 //authenticate() middleware  may  invoke req.login() automatically.
              |
              | calls
             \./  
 passport.serializeUser(function(user, done) {
        done(null, user.id); 
                     |
//use 'id'to serialize, you can use other or user object itself
    });              |-->saved to session req.session.passport.user = {id:'..'}
                     |
                     |__________________
                                       |          
    passport.deserializeUser(function(id, done) {
                      ________________|
                      | 
        User.findById(id, function(err, user) {
            done(err, user);
                       |______________>user object ataches to the request as req.user

     });
      });

here id key can be any key of the user object ie name,email etc

A.B
  • 20,110
  • 3
  • 37
  • 71
  • 1
    I am not getting one thing, serialize is storing req.session.passport.user = {id:'xyz'} this. Then who is storing the user in mongo DB. and what if I just want to store the session in memory? – mannuscript Mar 17 '15 at 03:09
  • @Mannu see if it makes more sense now – A.B Mar 17 '15 at 15:36
  • sorry for the question, which is the different useing "id" or "object" itself as key? – francesco.venica Jun 11 '15 at 22:51
  • @kikko088 no difference you can find complete user in mongodb or any source if you haveused complete object in serialize done() function passport.deserializeUser(function(user, done) { User.findById(user, function(err, user) { done(err, user); }); }) – A.B Jun 11 '15 at 23:20
13

Given the following configuration of express-session with connect-redis as the session store (using Express 4):

redis = require('redis').createClient(6379, '127.0.0.1');
session = require('express-session');
RedisStore = require('connect-redis')(session);

app.use(session({
  store: new RedisStore({
    client: redis
  }),
  secret: 's3cret',
  resave: true,
  saveUninitialized: true
}));

You can just tell passport to serialize the entire user object, instead of just the user id.

passport.serializeUser(function(user, done){
  done(null, user);
});

passport.deserializeUser(function(user, done){
  done(null, user);
});

The entire user object will be saved with the session in Redis, and placed on the request as req.user for every request.

Andrew Homeyer
  • 7,937
  • 5
  • 33
  • 26
  • 1
    The other comments were helpful in understanding but this was the best solution for me in the end since I'm also using Redis, nice and easy – ObjectiveTruth Apr 18 '15 at 15:48
  • 1
    Could you explain how do you update users data if they get changed? – Erik Jun 17 '15 at 14:34