PassportJS is awesome. It provides an abstraction layer over logging in with various providers such as Facebook, Google, Github, Twitter and more.
When first getting started though it can be a little challenging to understand what’s going on and why. Their documentation is pretty good but leaves out some specifics that I initially found difficult to understand. Hopefully this helps answer some of your questions and clears up some confusion. I assume you’ve read the docs a little and have maybe tried implementing it. Even if you haven’t though, you should still be able to follow along. Even if you have, this should still prove useful in understanding PassportJS a little better.
Here’s what’s covered
- The Callback Function in Strategy Setup
passport.authenticate()is needed in the callback
- Checking if the User is Logged in
Alright, lets go!
Issue 1: Understanding the Callback in Strategy Setup
Take a look at the code below. We require our modules, set up our google strategy and create two routes, one for handling logging in and another for the
When a user visits
/lgoin/google, we authenticate with google and in this case, request their
One key part though is, as part of
passport.authenticate(), the function on line 9 is called before the
console.log() is on line 15.
This function is the first time you have access to the profile information. Typically this is where you would add the user to your DB if it doesn’t already contain it (See code example below). But how do you exit this function to get back to your code, specifically to the route handler for the
callbackURL specified on line 19.
To do this you call
done()? An internal PassportJS function. You pass it two arguments, null and the profile info like this:
This takes the profile info and attaches it on the request object so its available on your callback url as
req.user. This will be available for the route
/login/google/return as defined on line 41 and this is where you would set the session for the user and then send them on their way to another part of your site.
Issue 2: Why the passport.authenticate() in the callbackURL?
If a user makes it back to
/login/google/return, why do we need another
passport.authenticate()? This is partly to make sure the user didn’t just go to the route directly and verifies that they are in fact logged in. This is handled internally by PassportJS.
As a side note, you should not use
passport.authenticate()as a method to check if a user is logged in. This should only be used to actually log a user in which is needed in this case since they might not yet be logged in if they just typed in the url and route and hit enter.
Issue 3: Check if a User is Logged In
On line 36 in the code block above we set
req.session.user to equal the profile of the person who just logged in. To check if someone is logged in we can just see if this value has been set and then use this function as middleware on any route we want where the user has to be logged in. Here is an example of the middleware function.
Then if you want to ensure a user is logged in for a route, just do this
This will call
req.session.user is set then the request continues on due to the
next() call. Otherwise the user is redirected to
Issue 4: What is this Serialization and Deserialization Business?
From above we said that passport attaches the profile information to
req.user. This occurs as a direct result of the
When you write
done() in the callback function specified when setting up the strategy (look at the second code block all the way at the top where we do
done(null, dbUserRecord or
done(null, newUser)), we pass in the entire user profile object (in this example as either
dbUserRecord or as
The reason for this is because it is received by
serializeUser(). This function then calls
done(null, user.id). Passport takes that user id and stores it internally on
req.session.passport which is passport’s internal mechanism to keep track of things.
deserializeUser(), the first argument is an
id which is the same
id that was passed in
done(null, user.id) of
deserializeUser() then makes a request to our DB to find the full profile information for the user and then calls
done(null, user). This is where the user profile is attached to the request handler at
req.user. Then finally after all this occurs, the user is routed back to the
/login/google/return route handler where we can finally access the user profile information on
To understand this user flow a little better take a look at the diagram on this StackOverflow answer: https://stackoverflow.com/questions/27637609/understanding-passport-serialize-deserialize?answertab=votes#tab-top.
You might ask yourself now, why is all this necessary. Handling user info in this manner means PassportJS only has to store the user id and not the entire user profile. This decreases the likelihood of confusing
req.session.user which is something we set and
req.session.passport.user which is PassportJS’s way of keeping track of what’s going on.
This can be seen by logging
req.session which will return something like this
passport.user is the same as
user._id. They have to be so we can perform the database lookup with the right information. Further
passport.user only stores the id and nothing else like
googleID which we see in the
PassportJS makes it really easy to get setup but understanding its data flow can be confusing. I hope this helps explain some of the underlying principles it uses. If you have any questions please leave them in the comments.
Thanks for reading!