15

I'm using passportJS with express to authenticate user by local strategy. I have seen few articles regarding how passport is setup and the execution flow. Although most of the thing regarding passport can be figured out by searching, there is serialization and deserialization of user which keeps me confused.

I understand it is used to save the user information in session for persistent login. My code for serialization and deserialization is

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

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

My question regarding this

1) Who calls and populates the arguments of the serializeUser and deserializeUser? And how it has access to the user object? To understand this I added log like

 passport.serializeUser(function(user, done){
    console.log(arguments.callee.caller);
    done(null, user.id);
});

And got [Function: pass] in output Can anyone explain this?

2) I am using mongodb to store the user information. MongoDB has _id as the default id of document. So ideally the serializeUser and deserializeUser should have worked with user._id instead of user.id. But it is working fine with user.id which is not available in User the object. Here is the user object printed in console

{ _id: 5505f231b810dbd4098ac76a,
  __v: 0,
  google: {},
  twitter: {},
  facebook: {},
  local:
   { password: '$2a$08$9NGd0xNu0JbWMZ07ufyFRu8guwy147k8IBl5cAC4Y8APOuxreNI32',
     email: 'xxxx@xxx.com' } }

How is this possible?

3) Where the control flow execution goes once done method is executed?

years_of_no_light
  • 938
  • 1
  • 10
  • 24

3 Answers3

30

After a long time of searching I found this article which explains authentication flow very clearly.

  • For serializeUser:
  1. When the user submits the login form, a POST request to /login is made resulting in the execution of the passport.authenticate middleware we've set up.
  2. As the authenticate middleware for that route is configured to handle the local strategy, passport will invoke our implementation of the local strategy.
  3. Passport takes the req.body.username and req.body.password and passes it to our verification function in the local strategy.
  4. Now we do our thing: loading the user from the database and checking if the password given matches the one in the database.
  5. If everything went fine and we want the user to login we invoke done(null, user).
  6. Calling done will make the flow jump back into passport.authenticate. It's passed the error, user and additional info object (if defined).
  7. If the user was passed, the middleware will call req.login (a passport function attached to the request).
  8. This will call our passport.serializeUser method we've defined earlier.
  • For deserializeUser:
  1. Express loads the session data and attaches it to the req. As passport stores the serialised user in the session
  2. passport.session middleware is a Passport Strategy which will load the user object onto req.user if a serialised user object was found in the server.
  3. passport.initialize is invoked on the request, it finds the passport.user attached to the session. Next, passport.session is invoked.
  4. The passport.session middleware calls passport.deserializeUser we've setup. Attaching the loaded user object to the request as req.user.

I hope it helps.

Community
  • 1
  • 1
Jalal
  • 3,308
  • 4
  • 35
  • 43
  • After spending almost 30 hours going through endless permutations of code, at no point is `deserializeUser` triggered. – Wayne Smallman Aug 24 '19 at 18:43
  • 1
    This is a nice article but you dropped an important piece, for people who struggle with these call's function. `8. This will call our passport.serializeUser method we've defined earlier. This method can access the user object we passed back to the middleware. It's its job to 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 = { // our serialised user object // }.` – Will59 Apr 24 '20 at 10:04
3

Since you are using PassportJS so i assume you must be having some idea about how it works. So i would add further information which i think would clear your doubt.

Passport configuration involves three pieces:

  1. Authentication strategies
  2. Application middleware
  3. Sessions

The answer to your question lies in 3rd piece, sessions.

If authentication succeeds, a session will be established and maintained via a cookie set in the user's browser. Each subsequent request will not contain credentials, but rather the unique cookie that identifies the session. In order to support login sessions, Passport will serialize and deserialize user instances to and from the session.

According to your implementation only the user ID is serialized to the session, keeping the amount of data stored within the session small. When subsequent requests are received, this ID is used to find the user, which will be restored to req.user

In passports we are given option to write our own serialization and deserialization logic so that we can choose any appropriate database and not tied with strict rules.

So to summarise, after successful authentication, user object is serialised and stored in session, if you call req.user, then you would be able to retrieve the same user object.

NarendraSoni
  • 2,210
  • 18
  • 26
  • I understand what you are stating. But my queries are some what different. :) – years_of_no_light Mar 16 '15 at 08:04
  • Hi, Really great explanation but is this possible to change the info in serializeUser mid-session , because this is set when once user is logged in.. So what i want is to set two things in cookie but one thing is dynamic – Jaskaran Singh Jun 11 '20 at 14:25
1

You are using cookie based authentication. To answer it for you and maybe someone who is very new at everything, I will try to be very very detailed. So here's a scenario...

  • User comes in and say: "I wanna log in, here's my, say, google profile."
  • Our server then says: "Okay, we are going to run the google strategy callback function that we have created, and process the profile that we just got from you..."

Let's imagine that, this user has already logged into our application previously, so there's a record inside of our Users collection that matches this profile, so server says: " Looks like you have the same google profile ID as user123(that we have saved on our DB). I need to give you some identifying token that says you are without a doubt: 'user123' and this token will identify you on any follow up request that is made to our servers"

At that point, to generate that little token or little identifying piece of information, we are going to define a function called 'serializeUser.' serializeUser is going to be automatically called by passport with our user model that we just fetched during the this very last step. So to explain, we're going to use that user model to generate our identifying piece of user information and after we do that we'll pass that identifying piece of information back to passport and so that passport can automatically stuff that little token into the user's cookie for us.

Now, once the user decides that they want to make some type of follow up request, for instance a list of post or a list of messages or whatever, from the browser back to our server, the cookie for that request will be automatically added in the request by the browser, passport is going to take that identifying piece of information from the cookie and then pass it into a second function called "deserializeUser," in which we are going to take that identifying token and turn it back into a user model that uniquely identify this user.

To conclude...

In the first process (seralizeUser), 'user' was a user model instance (a mongoose model, if using mongoose)... What we did is we turned that model into an id.

In the second process (deserializeUser), we are doing the exact opposite, turning an id into a mongoose model instance. And to do that we have to search or query our big'ol collection with all of our different users that exist inside of our DataBase (using findById), and after we find that very particular user, then we will call 'done' on that user, meaning we will return that user.

Mr. Carter
  • 11
  • 1