481

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.

Sunil Garg
  • 14,608
  • 25
  • 132
  • 189
Anubhav
  • 7,138
  • 5
  • 21
  • 33

7 Answers7

639
  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'}

  1. 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   
});
cmbuckley
  • 40,217
  • 9
  • 77
  • 91
A.B
  • 20,110
  • 3
  • 37
  • 71
  • 5
    So is `user.id` saved as `req.session.passport.user` or is `user` itself stored as `req.session.passport.user` – Anubhav Dec 24 '14 at 13:47
  • @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? – uzay95 Feb 06 '16 at 09:26
  • object is retrieved from req.user afterwards :) @uzay95 – A.B Apr 08 '16 at 10:46
  • So 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? – leofontes Jun 24 '16 at 11:51
  • 2
    @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? – Zanko Jul 21 '16 at 07:09
  • 24
    @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. – Max Truxa Aug 22 '16 at 18:30
  • is passortjs a cookie-based Authentication OR Token-based Authentication? – Internial Oct 22 '17 at 20:42
  • I still did not get what is the benefit of `deSerializeUser` method? – Sunil Garg Nov 03 '17 at 07:32
  • Sir, Can we talk I have few doubts, It would be a great help if I could talk to you – Suraj Jain Jan 01 '18 at 12:10
  • @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? – Valamorde Apr 26 '18 at 11:59
  • 1
    @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 – A.B Apr 26 '18 at 19:49
  • @A.B thanks but I already get that, I was looking for it's original callback meaning where the value is returned to! – Valamorde Apr 26 '18 at 20:40
  • Wonderful 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 as `req.user`. I havent ever used `req.session.passport.user` although my `done` methods are exactly the same. Whenever I want the user I just grab it from `req.user` – Rishav May 24 '19 at 16:46
  • @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) – A.B May 27 '19 at 10:09
  • @A.B How did I miss that. Sorry. Also say I name it as `foo` instead `user` will it be attached as `req.foo`? – Rishav May 27 '19 at 16:50
  • 4
    If I'm not mistaken, the `req.session.passport.user = {id: '..'}` part of the diagram is slightly off, and should be `req.session.passport.user = 785352` instead, where `785352` is `user.id`. I'm having trouble console logging to prove it, but it seems like it'd make sense. When you call `done(null, user.id);`, it'd make sense to take the second argument - `user.id` in this case - and assign it to `req.session.passport.user`, instead of assigning it to `req.session.passport.user.id`. Because what if you instead pass in `user`? `req.sesssion.passport.user.id = user` wouldn't make sense. – Adam Zerner May 27 '19 at 19:17
  • Suppose that the ```user``` object passed to ```serializeUser()``` 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 within```serializeUser()```? 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 Feb 29 '20 at 23:20
  • I 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 the ```profile``` 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. – SpeedGolfer Mar 01 '20 at 00:13
  • referencing a great blog post https://hackernoon.com/passportjs-the-confusing-parts-explained-edca874ebead for further overall clarity – Kunal Aug 19 '20 at 22:12
  • could you inform us which tool you have used to make the flow diagram? – anakha Dec 18 '20 at 07:37
  • @A.B Do you know why `passport.deserializeUser` is called [after](https://stackoverflow.com/questions/65374696/why-is-passport-deserializeuser-called-after-req-user-has-been-restored) `req.user` has been restored? – user2309803 Dec 20 '20 at 07:44
  • @anakha I didnt use any tool and draw it manually, but there might be some tools now – A.B Dec 23 '20 at 08:42
  • 1
    `passport.initialize()` returns a middleware that among other things [configures the `session` strategy](https://github.com/jaredhanson/passport/blob/v0.4.1/lib/authenticator.js#L32). `passport.session()` returns a middleware that [wraps](https://github.com/jaredhanson/passport/blob/v0.4.1/lib/authenticator.js#L233) the `session` strategy. The `session` strategy takes user id from the session, passes it to the deserialize callback, and puts the result into [`req.user`](https://github.com/jaredhanson/passport/blob/v0.4.1/lib/strategies/session.js#L67). – x-yuri Feb 08 '21 at 00:52
  • Well, actually the `session` strategy is configured when you [require `passport`](https://github.com/jaredhanson/passport/blob/v0.4.1/lib/index.js#L13). The `initialize` middleware does [something different](https://github.com/jaredhanson/passport/blob/v0.4.1/lib/middleware/initialize.js#L45-L51). – x-yuri Feb 08 '21 at 01:11
  • What is the `User` object in the `User.findById(id, function(err, user) ` line? Where does that come from? Is it imported? If so, from where? – lowcrawler May 12 '21 at 21:46
  • is it save to pass the whole user object (including password) to the done function as a second augument ? like this: passport.deserializedUser((id,done)=>done(null, getUserById(id))) – Kashif Ali Jan 17 '23 at 16:40
  • is it safe for include password in user object at deserialize? Because i have middleware res.locals.user = req.user for access user from view and it have password. -- passport.deserializeUser((id, done) => { User.findById(id, (err, user) => { done(err, user); }) }); – Fırat Taşkın Apr 17 '23 at 06:40
22

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

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
yvanscher
  • 1,009
  • 1
  • 13
  • 16
  • Sorry 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: https://stackoverflow.com/questions/54154047/simple-auth-with-passport-local-and-deserializeuser-problem – Peter Kellner Jan 11 '19 at 21:09
  • Super helpful, but still having some issues reading the user from other routes. Can anyone help me out here? https://stackoverflow.com/questions/60709882/koa-passport-oauth2-save-token-to-state – Harry Lincoln Mar 16 '20 at 17:20
22

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.stringify(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.
You Qi
  • 8,353
  • 8
  • 50
  • 68
Vadi
  • 3,279
  • 2
  • 13
  • 30
2

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.

2

So I got stuck on this for quite awhile so I made a quick setup file so no one ever gets stuck on this garbage ever again

passportmgmt.js

const localStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');
const User = require('./db-schemas/models').UserModel;
/*^^^^^^^^^^^ delete above import and modify below definition as needed 
const mongoose = require('mongoose')
mongoose.connect("mongodb://localhost:27017/database_name");

const UserSchema = new mongoose.Schema({
    username: {
        type: String,
        required: true
    },
    password: {
        type: String,
        required: true
    },
    email: {
        type: String,
        required: true
    }
});
const UserModel = mongoose.model('collection_name', UserSchema);
^^^^^^^^^^^^^^^ Change UserModel to User */
const passport = require('passport');

function passportSetupUwU(app) {
    const cookieParser = require('cookie-parser');
    const session = require('express-session');
    const MongoDBStore = require('connect-mongodb-session')(session);

    app.use(cookieParser());
    app.set('trust proxy', 1);
    app.use(session({
    secret: "secret",
    resave: false,
    saveUninitialized: false,
    store: new MongoDBStore({
        uri: "mongodb://localhost:27017/database_name",
        collection: "collection_name"
    })
    }));

    // Passport.js
    app.use(passport.initialize());
    app.use(passport.session());
}

passport.serializeUser(function(user, cb) {
    console.log("serializing user uwu:" + JSON.stringify(user))
    process.nextTick(function() {
        return cb(null, user.id)
    })
})

passport.deserializeUser(function (id, cb) {
    console.log("deserializing user owo:" + JSON.stringify(id))
    User.findOne({id:id}, function (err, user) {
        if (err) { return cb(err)}
        return cb(null, user);
    })
});

passport.use(new localStrategy(function (username, password, done) {
    console.log("attempted login with %s:%s", username, password);
    User.findOne({ username: username }, function (err, user) {
        if (err) return done(err);
        if (!user) return done(null, false, { message: 'No such user exists.' });

        bcrypt.compare(password, user.password, function (err, res) {
            if (err) return done(err);
            if (res === false) return done(null, false, { message: 'Incorrect password.' });
            
            return done(null, user);
        });
    });
}));

module.exports = {setup: passportSetupUwU}

now just import and run the passportSetupUwU function in app.js and you're good to go:

app.js exerpt

// some example middleware and stuff i happened to have
const PPSetup = require('./passportmgmt').setup
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));

//
// <- PUT IT LIKE HERE OR SOMETHING
//
PPSetup(app);

// some example routes and api endpoints i happened to have
app.use('/api/', apiRouter);
apiRouter.use('/', indexRouter);
apiRouter.use('/users', usersRouter);

apiRouter.post('/login', passport.authenticate('local', {
Kelvin Wang
  • 627
  • 5
  • 21
1

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);
          });
        });
bguiz
  • 27,371
  • 47
  • 154
  • 243
0

A visual workflow for Passport's serialize and deserialize methods:

  1. User logs in to your application and provides their credentials.
  2. Passport uses the serializeUser function to take the user object and convert it into a session object.
  3. Passport stores the session object on the server and sends a session cookie to the user's browser. 4.User makes a request to your application. 5.Passport uses the deserializeUser function to take the session object and convert it back into a user object.
  4. Passport passes the user object to your application, which can then use it to determine if the user is authenticated or not.
  5. If the user is authenticated, your application will allow them to access the requested resource.
  6. If the user is not authenticated, your application will deny access and redirect them to the login page.

Here's a visual representation of the workflow:

    +------------+             +---------------+
|            |             |               |
| User Login |             | Session Object |
|            |    serializeUser    |               |
+-----+------+   ------------------>   +---------+
      |                                 |           |
      |                                 | Store on  |
      |                                 | Server    |
      |                                 |           |
+-----+------+   <------------------   +---------+
|            |             |               |
| User Request |    deserializeUser    |               |
|            |   ------------------>   |             |
+------------+             |               +-----------+
      |                   |
      |                   |
      |                   |
+-----+------+   <------------------   +---------+
|            |             |               |
| Authenticated |             |     User     |
|  or not       |             |             |
|            |             |               |
+------------+             +---------------+

I hope this helps clarify the workflow of Passport's serialize and deserialize methods!

ADITYA NAYAK
  • 11
  • 1
  • 3