jdogdvr jdogdvr - 6 months ago 10
Node.js Question

Node.js auth using passport.js confusion with syntax?

Hi guys there are a few things in passport.js that I don't understand.

1).



var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy;

passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));





what does
null
represent in the
done()
function. It seems to be the first parameter always and I am confused about what is actually does?

2)



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

passport.deserializeUser(function(obj, cb) {
cb(null, obj);
});





What does serialize and deserialize do? Does serialize get called after a login when session is stored in the browser? and deserialize is when a page is accessed so the session is deserialized on the server to validate that user?

Lastly what is the
null
parameter again in
cb(null, user);

Answer
  1. what does null represent in the done() function. It seems to be the first parameter always and I am confused about what is actually does?

    • what is the null parameter again in cb(null, user);

By convention, NodeJS uses error-first callbacks which means the first argument to callback functions is always the error object. If you don't have any errors, you pass in null. In other words, if the error argument is null, then the operation was successful and if the error argument is not null, then an error has occurred. This holds true for all the examples you asked about. If you look at the code you can see that you are already leveraging this:

User.findOne({ username: username }, function (err, user) {
  if (err) { 
    // Error happened and passed as first argument
    return done(err); 
  } 

  // ...

  // no error so we pass in null
  return done(null, user);

Furthermore, Passport has the convention for its authentication process where, as their docs say:

If the credentials are valid, the verify callback invokes done to supply Passport with the user that authenticated. If the credentials are not valid (for example, if the password is incorrect), done should be invoked with false instead of a user to indicate an authentication failure.

That's what you are doing here:

User.findOne({ username: username }, function (err, user) {

  // ...

  if (!user) {
    return done(null, false, { message: 'Incorrect username.' });
  }
  if (!user.validPassword(password)) {
    return done(null, false, { message: 'Incorrect password.' });
  }

  // ...

  1. What does serialize and deserialize do? Does serialize get called after a login when session is stored in the browser? and deserialize is when a page is accessed so the session is deserialized on the server to validate that user?

Well, Passport's documentation says:

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.

This means that, after the user logs in, serializeUser is called and the user data you pass to the callback cb:

passport.serializeUser(function(user, cb) {
  cb(null, user); // <-- this user object
});

is saved in the session store (usually browser cookies) and made available at req.session.passport.user in your code.

When the user reconnects to your page (either by refreshing or leaving and coming back), this same data is passed as the first parameter to deserializeUser to be used retrieve the user object.

passport.deserializeUser(function(obj, cb) {
  cb(null, obj); // <-- obj is the same `user` object you used in serializeUser
});

What you are doing here is passing the actual user object to the callback in serializeUser and then passing that same object back through the callback in deserializeUser. This means you are storing the entire user object in your cookies which is ok for playing around but not generally a good idea since cookie storage is limited plus user information is usually sensitive.

The typical way to do this is to pass the user id, not the whole user object, to cb in serializeUser to keep the amount of data stored within the session small. When further requests happen, this id is passed to deserializeUser and used to find the actual user object, usually from the database, which will be restored to req.user.

Here's an example:

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

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