Passport: Document req.login for refreshing req.user

Created on 26 Feb 2014  路  17Comments  路  Source: jaredhanson/passport

I hit a use case where I needed to refresh req.user with new information without logging in and out. Somebody on Stack Overflow pointed me to calling req.login() with the updated values. However nowhere is this in the documentation--would love to see it there!

Most helpful comment

The Login Flow of Passport

Login Flow

  1. You run your authentication in your passport strategy login code. The authentication will go to the database to find the user and create a User object.
  2. With the User object, authentication will check to see if the user exists and if the password is correct. If the check is good then authentication is done and returns the User object.
  3. Passport will then run serializeUser and pass in the User object. Behind the scenes, passport will load the User object into session req.user and also save only the unique ID of the User object into the cookie.

(1) Login --> [2] Database --> (3) serializeUser --> [4] Session --> [5] Cookie

Authenticated Flow

  1. If the user is already logged in and makes a request, it will follow the "authenticated flow", which is instead of serializing the user, you deserialize the user. But the difference is, deserializeUser is called a lot more times because it's called once per request.
  2. Passport gets the ID from the cookie and run deserializeUser and pass in the ID. It will go to the database and find the user by its ID. Passport will recreate the User object and then return the User object.
  3. Behind the scenes, passport will then reload the User object into session req.user.

(1) Authenticated request --> [1] Cookie --> [2] Database --> (3) deserializeUser --> [4] Session

When User Info Has Changed Flow

1) Since passport is getting the user from the database for every request in the above "authenticated flow", this means the User object in session is always up-to-date.
2) Hence, there doesn't need to be any extra logic such as calling req.login to handle the case when the user info has changed.

Note: Yes, deserializeUser is called once per request. In other words, it is called many times for each page load.

[Edit: please read post below by chichilatte on calling req.login when user info has changed]

All 17 comments

Totally agree. This use case is as common as dandruff. Anytime a user changes a profile setting.

:+1:

:+1:

:+1: I have searching for this _official_ way for quite a while.

:+1: this use case might warrant a new function.

:+1:

I am also trying to update req.user with new information and i am updating it using following code

 req.login(userObj, function(err) {
    if (err) return next(err)

    console.log("After relogin: ");
    console.info(req.user);
  });

where userObj contains new updated user info. in the console i can see req.user has new data, but when i access it again on the route as req.user it shows old data, i am confused, what is wrong here..? can some expert throw some light..?

Thanks!
Regards
Naren

@narendra-agashe Have you figure out that? I have the same problem thank you cc @Zugwalt

The Login Flow of Passport

Login Flow

  1. You run your authentication in your passport strategy login code. The authentication will go to the database to find the user and create a User object.
  2. With the User object, authentication will check to see if the user exists and if the password is correct. If the check is good then authentication is done and returns the User object.
  3. Passport will then run serializeUser and pass in the User object. Behind the scenes, passport will load the User object into session req.user and also save only the unique ID of the User object into the cookie.

(1) Login --> [2] Database --> (3) serializeUser --> [4] Session --> [5] Cookie

Authenticated Flow

  1. If the user is already logged in and makes a request, it will follow the "authenticated flow", which is instead of serializing the user, you deserialize the user. But the difference is, deserializeUser is called a lot more times because it's called once per request.
  2. Passport gets the ID from the cookie and run deserializeUser and pass in the ID. It will go to the database and find the user by its ID. Passport will recreate the User object and then return the User object.
  3. Behind the scenes, passport will then reload the User object into session req.user.

(1) Authenticated request --> [1] Cookie --> [2] Database --> (3) deserializeUser --> [4] Session

When User Info Has Changed Flow

1) Since passport is getting the user from the database for every request in the above "authenticated flow", this means the User object in session is always up-to-date.
2) Hence, there doesn't need to be any extra logic such as calling req.login to handle the case when the user info has changed.

Note: Yes, deserializeUser is called once per request. In other words, it is called many times for each page load.

[Edit: please read post below by chichilatte on calling req.login when user info has changed]

@zhiyelee, please look at my post above on "When User Info has Changed Flow" which is a lot easier to do but not as good performance. Or please look at @chichilatte's post below which is a little more complicated but is better performance.

My way: If you want to update user info, you got to update the value in your database first. Do that first and deserializeUser() will automatically update the new value in session req.user. Because deserializeUser() actually looks-up the user in the database on every request and then, behind the scenes, it copies that user info into session. Please keep in mind it's for _every request_, that's why it's not good performance.

Chichilatte's way: You don't have to change any code in deserializeUser(), you just got to make sure you run req.login() right after you update the user in the database. Also keep in mind of the asynchronous nature of Node, so it's best you call req.login() inside the callback function of the database user update.

Awesome Post, thank you @samhuntsocial !!! That helps a lot.

@samhuntsocial Great description of Passport's flow. It really needs spelling out, since it's pretty complicated. However, it looks like you're saying you should keep the whole of a user's data in the session cookie, and then reload it from the database on every request (done in deserializeUser()). Surely that's a waste of database resources. I would either...

  • Keep the user in the session cookie, loading the user from the database only _once_ when the user logs in.
  • Keep just basic, frequently used user-data in the session cookie. Only load a user's complete userdata from the database when you need more information about them (e.g. a user-edit page). If a user's basic data is normally enough for you (e.g. id, name, language, profilePicId), I would go with this option.

In both cases, when a user changes their data, you'd save the user to the database then update the user in the session by calling req.login().

That's how I do it and it works for me. If you've got it working with another method then it's probably best to ignore me and optimise only when you really need to! +Sorry if this makes people more confused!

@chicilatte Thanks. I agree with you. Store only the user's ID in the cookie and store only pertinent info of the user in the session req.user.

In my production system, my logic actually more closely follows what you say. I load the user from the database _only once_ when the user logs in (due to better performance). The rest of the time, it's persisted in-memory in Node, in an array. I do have my doubts of the robustness of this in-memory approach (i keep thinking there will be bugs later), but so far the web application is holding up and I don't hit the database on _every request_ made by users.

The simplified way I explained above is because that's the way it is in Passport example codes in the internet, e.g. https://github.com/fabiobozzo/expressjs-multiauth

Yeh, the Passport example does seem to be the simplest to get your head around. As for using an array to store your user sessions, there are three reasons why that could be slightly crappy:

  • The main reason: when you deploy a new version of your app all your users are logged out. Totally annoying. Also happens when your app throws an error or you reset the server's operating system.
  • The number of logged-in users is limited by your server's memory.
  • It stops you from using more than one application server if you want to scale to a larger audience. But node is so quick you'll be doing fabulously well if you have to start using more than one app server :)

In my experience, it's best to use the "connect-redis" module to store your user sessions. Pretty easy to set up, and then you can have as many app servers as you like, all grabbing their user sessions from an independent, lighting quick Redis server. If you can't be arsed with that, storing your sessions on disk seems like the best option (I think this is built into Express, right?).

@chichilatte many thanks, very helpful. i think this "connect-redis" module is what i've been looking for to replace my "array". very good reasons, all three. i'm fully convinced. if it's a production environment, we must use caching like "connect-redis". if you're just testing out code in your development environment, it's OK to use arrays...but just don't forget to switch over to something like "connect-redis" as soon as you can.

@samhuntsocial @chichilatte Thanks for the clarification but as you said, Passport fires the deserializeUser function for every request which keeps the req.user up to date. If we are to use chichilatte method of only loading the user one time at the login stage then calling req.login just after we make updates in our database, how do we stop the deserializeUser function firing for every request?

Edit: From what I understand, do you serialise what you need in the session then when deserializeUser, you pass back whats in the session? So as I store my sessions in redis, they would basically come from there and don't need to go to the postgresql database.

@samhuntsocial The approach you presented would only work when immutable information about the user is stored in the session right such as user id? Because in the case where a modifiable field such as username is stored in the session, when the user updates said modifiable field the session data becomes outdated which results in the user being logged out since deserializeUser relies on the outdated session data. Just making sure I understand why your approach works, thanks.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

xingrz picture xingrz  路  5Comments

scan picture scan  路  7Comments

callumacrae picture callumacrae  路  5Comments

angel1st picture angel1st  路  7Comments

angel1st picture angel1st  路  5Comments