Understanding passport serialize deserialize

node.jsAuthenticationExpressSerializationpassport.js

node.js Problem Overview


How would you explain the workflow of Passport's serialize and deserialize methods to a layman.

  1. Where does user.id go after passport.serializeUser has been called?

  2. We are calling passport.deserializeUser right after it where does it fit in the workflow?

     // used to serialize the user for the session
     passport.serializeUser(function(user, done) {
         done(null, user.id); 
        // where is this user.id going? Are we supposed to access this anywhere?
     });
    
     // used to deserialize the user
     passport.deserializeUser(function(id, done) {
         User.findById(id, function(err, user) {
             done(err, user);
         });
     });
    

I'm still trying to wrap my head around it. I have a complete working app and am not running into errors of any kind.

I just wanted to understand what exactly is happening here?

Any help is appreciated.

node.js Solutions


Solution 1 - node.js

> 1. Where does user.id go after passport.serializeUser has been called?

The user id (you provide as the second argument of the done function) is saved in the session and is later used to retrieve the whole object via the deserializeUser function.

serializeUser determines which data of 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 the user id as the key) req.session.passport.user = {id: 'xyz'}

> 2. We are calling passport.deserializeUser right after it where does it fit in the workflow?

The first argument of deserializeUser corresponds to the key of the user object that was given to the done function (see 1.). So your whole object is retrieved with help of that key. That key here is the user id (key can be any key of the user object i.e. name,email etc). In deserializeUser that key is matched with the in memory array / database or any data resource.

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

Visual Flow

passport.serializeUser(function(user, done) {
    done(null, user.id);
});              │
                 │ 
                 │
                 └─────────────────┬──→ saved to session
                                   │    req.session.passport.user = {id: '..'}
                                   │
                                   ↓           
passport.deserializeUser(function(id, done) {
                   ┌───────────────┘
                   │
                   ↓ 
    User.findById(id, function(err, user) {
        done(err, user);
    });            └──────────────→ user object attaches to the request as req.user   
});

Solution 2 - node.js

For anyone using Koa and koa-passport:

Know that the key for the user set in the serializeUser method (often a unique id for that user) will be stored in:

this.session.passport.user

When you set in done(null, user) in deserializeUser where 'user' is some user object from your database:

this.req.user OR this.passport.user

for some reason this.user Koa context never gets set when you call done(null, user) in your deserializeUser method.

So you can write your own middleware after the call to app.use(passport.session()) to put it in this.user like so:

app.use(function * setUserInContext (next) {
  this.user = this.req.user
  yield next
})

If you're unclear on how serializeUser and deserializeUser work, just hit me up on twitter. @yvanscher

Solution 3 - node.js

Passport uses serializeUser function to persist user data (after successful authentication) into session. Function deserializeUser is used to retrieve user data from session.

Both serializeUser and deserializeUser functions check first argument passed to them, and if it's of type function, serializeUser and deserializeUser do nothing, but put those functions in a stack of functions, that will be called, afterwards (when passed first arguments are not of type function). Passport needs the following setup to save user data after authentication in the session:

app.use(session({ secret: "cats" }));
app.use(passport.initialize());
app.use(passport.session());

The order of used middlewares matters. It's important to see, what happens, when a new request starts for authorization:

  • session middleware creates session (using data from the sessionStore).

  • passport.initialize assigns _passport object to request object, checks if there's a session object, and if it exists, and field passport exists in it (if not - creates one), assigns that object to session field in _passport. At the end, it looks, like this:

    req._passport.session = req.session['passport']
    

    So, session field references object, that assigned to req.session.passport.

  • passport.session looks for user field in req._passport.session, and if finds one, passes it to deserializeUser function and calls it. deserializeUser function assigns req._passport.session.user to user field of request object (if find one in req._passport.session.user). This is why, if we set user object in serializeUser function like so:

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

    We then need to parse it, because it was saved as JSON in user field:

     passport.deserializeUser(function(id, done) {
       // parsed user object will be set to request object field `user`
       done(err, JSON.parse(user));
     });
    

So, deserializeUser function firstly called, when you setup Passport, to put your callback in _deserializers function stack. Second time, it'll be called in passport.session middleware to assign user field to request object. That also triggers our callback (that we put in passport.deserializeUser()) before assigning user field.

serializeUser function called first, when you setup Passport (similarly to deserializeUser function), but it'll be used to serialize user object for saving in session. Second time, it'll be called, in login/logIn (alias) method, that attached by Passport, and used to save user object in session. serializeUser function also checks _serializers stack with already pushed to it functions (one of which added, when we set up Passport):

passport.serializeUser(function(user, done) ...

and calls them, then assigns user object (strignified) or user id to req._passport.session.user. It is important to remember that session field directly references passport field in req.session object. In that way user saved in session (because req._passport.session references object req.session.passport, and req._passport.session is modified in each incoming request by passport.initialize middleware). When request ends, req.session data will be stored in sessionStore.

What happens after successful authorization, when the second request starts:

  • session middleware gets session from sessionStore, in which our user data already saved
  • passport.initialize checks if there's session and assigns req.session.passport to req._passport.session
  • passport.session checks req._passport.session.user and deserializes it. At this stage (if req._passport.session.user is truthy), we'll have req.user and req.isAuthenticated() returns true.

Solution 4 - node.js

You can upgrade the old serialize and deserialize with this code, please up this post for new solution.

        passport.serializeUser(function(user, cb) {
          process.nextTick(function() {
            cb(null, { id: user.id, username: user.username });
          });
        });
        
        passport.deserializeUser(function(user, cb) {
          process.nextTick(function() {
            return cb(null, user);
          });
        });

Solution 5 - node.js

Basically, we are just storing the user-id in the session using serializer and when we need the user model instance, we use that user-id to search in the database which is done using deserializer.

As long as the session is active and the user is authenticated,

req.session.passport.user

will always correspond to user model instance.

If we don't save user-id into the session and if there is any redirect, we will have no way to know if the user is authenticated.

once the user is authenticated req.session.passport.user will be set. and hence all future requests will know that the user has been authenticated.

hope this simplifies.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAnubhavView Question on Stackoverflow
Solution 1 - node.jsA.BView Answer on Stackoverflow
Solution 2 - node.jsyvanscherView Answer on Stackoverflow
Solution 3 - node.jsVadiView Answer on Stackoverflow
Solution 4 - node.jsemindemirhanView Answer on Stackoverflow
Solution 5 - node.jsRaviraj GardiView Answer on Stackoverflow