Understanding passport serialize deserialize
Solution 1
- Where does
user.id
go afterpassport.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'}
- 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
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
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 fieldpassport
exists in it (if not - creates one), assigns that object tosession
field in_passport
. At the end, it looks, like this:req._passport.session = req.session['passport']
So,
session
field references object, that assigned toreq.session.passport
. -
passport.session
looks foruser
field inreq._passport.session
, and if finds one, passes it todeserializeUser
function and calls it.deserializeUser
function assignsreq._passport.session.user
touser
field of request object (if find one inreq._passport.session.user
). This is why, if we set user object inserializeUser
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
inuser
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 fromsessionStore
, in which our user data already saved -
passport.initialize
checks if there's session and assignsreq.session.passport
toreq._passport.session
-
passport.session
checksreq._passport.session.user
and deserializes it. At this stage (ifreq._passport.session.user
is truthy), we'll havereq.user
andreq.isAuthenticated()
returnstrue
.
Anubhav
Updated on June 17, 2021Comments
-
Anubhav almost 3 years
How would you explain the workflow of Passport's serialize and deserialize methods to a layman.
Where does
user.id
go afterpassport.serializeUser
has been called?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.
-
Anubhav over 9 yearsSo is
user.id
saved asreq.session.passport.user
or isuser
itself stored asreq.session.passport.user
-
uzay95 over 8 years@A.B I wrote code to find user from the id which has been passed to deserialize method as first parameter. But in every request it is retrieving user from db. This makes performance loss for db. What else should I write to deserialize function to check whether if it is exist on session or not?
-
A.B about 8 yearsobject is retrieved from req.user afterwards :) @uzay95
-
leofontes almost 8 yearsSo later on in my program, whenever I need information from the session, calling req.user.id should give me back the user in session and by consequence, call deserializeUser, correct?
-
Zanko almost 8 years@A.B I do not understand what you suggested to uzay95. So in my session I have only user._id. But on every request, I have to use that id to deserialized from database aka findUserByID and that will put it into req.user. How do I avoid making such call on every request?
-
Max Truxa over 7 years@Zanko You could put the whole user object into the session data, but that is usually not a good idea because it can have other side effects. For example, when the user updates his/her username you have to update the session data too, otherwise you'll get tickets because of "the broken rename feature". That's a relatively harmless example. Same could happen with permission bits or equal sensitive data (Oops...). Essentially the same problems you always run into if you have duplicate data. TL;DR - Don't do it.
-
Internial over 6 yearsis passortjs a cookie-based Authentication OR Token-based Authentication?
-
Sunil Garg over 6 yearsI still did not get what is the benefit of
deSerializeUser
method? -
Suraj Jain over 6 yearsSir, Can we talk I have few doubts, It would be a great help if I could talk to you
-
Valamorde about 6 years@A.B I know it's been quite some time since you answered this but could you clarify where the callback
done(err, user);
points to? -
A.B about 6 years@Valamorde Done is a callback function managed internally by passport and it takes you to the next step , success/failure etc depending on the parameter. Consider it similar to calling next for ease of understanding
-
Valamorde about 6 years@A.B thanks but I already get that, I was looking for it's original callback meaning where the value is returned to!
-
Peter Kellner over 5 yearsSorry for necroposting here, but I do have a concern now after reading the deserialize explanation. I've posted a question about this here on SO: stackoverflow.com/questions/54154047/…
-
Rishav almost 5 yearsWonderful answer. Although I still wonder how and when does
req.user
come into the picture?req.user
looks like{ _id: 5ca6153dd508054604805e55, username: 'Rishav Rungta', googleId: '105955******623074113', avatar: 'https://lh5.googleusercontent.com/-gZZ***', __v: 0 }
I have this data schema ofc but I really dont know when do I put this in asreq.user
. I havent ever usedreq.session.passport.user
although mydone
methods are exactly the same. Whenever I want the user I just grab it fromreq.user
-
A.B almost 5 years@Rishav it has been very long that i havent looked into it, but i think as it mentioned in the answer (just at the bottom), user object attaches to the request as req.user in deserialize method (when done is called)
-
Rishav almost 5 years@A.B How did I miss that. Sorry. Also say I name it as
foo
insteaduser
will it be attached asreq.foo
? -
Adam Zerner almost 5 yearsIf I'm not mistaken, the
req.session.passport.user = {id: '..'}
part of the diagram is slightly off, and should bereq.session.passport.user = 785352
instead, where785352
isuser.id
. I'm having trouble console logging to prove it, but it seems like it'd make sense. When you calldone(null, user.id);
, it'd make sense to take the second argument -user.id
in this case - and assign it toreq.session.passport.user
, instead of assigning it toreq.session.passport.user.id
. Because what if you instead pass inuser
?req.sesssion.passport.user.id = user
wouldn't make sense. -
SpeedGolfer about 4 yearsSuppose that the
user
object passed toserializeUser()
contains data on a brand new user who is not yet in the database. In that case, shouldn't we add the new user to the database withinserializeUser()
? Otherwise, how will we be able to add to our DB those fields of the user object obtained from the OAuth provider that we need to include in the user's database record (e.g.,profileImageUrl
,provider
, etc.)? -
SpeedGolfer about 4 yearsI think I can answer my own question! The place where we add new users to the database is in the callback function defined when we instantiate a new strategy:
function(accessToken, refreshToken, profile, done)
. I believe theprofile
parameter contains the profile info obttained from the strategy (e.g., a third-party OAuth provider such as GitHub). In this callback, we can check whether the user with this profile exists in our database and, if not, we can add the user. -
Harry Lincoln about 4 yearsSuper helpful, but still having some issues reading the user from other routes. Can anyone help me out here? stackoverflow.com/questions/60709882/…
-
Kunal over 3 yearsreferencing a great blog post hackernoon.com/… for further overall clarity
-
anakha over 3 yearscould you inform us which tool you have used to make the flow diagram?
-
user2309803 over 3 years
-
A.B over 3 years@anakha I didnt use any tool and draw it manually, but there might be some tools now
-
x-yuri about 3 years
passport.initialize()
returns a middleware that among other things configures thesession
strategy.passport.session()
returns a middleware that wraps thesession
strategy. Thesession
strategy takes user id from the session, passes it to the deserialize callback, and puts the result intoreq.user
. -
x-yuri about 3 yearsWell, actually the
session
strategy is configured when you requirepassport
. Theinitialize
middleware does something different. -
lowcrawler almost 3 yearsWhat is the
User
object in theUser.findById(id, function(err, user)
line? Where does that come from? Is it imported? If so, from where?