Open aarsilv opened 10 years ago
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
User
object. 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.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
deserializeUser
is called a lot more times because it's called once per request. 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. User
object into session req.user
.(1) Authenticated request --> [1] Cookie --> [2] Database --> (3) deserializeUser
--> [4] Session
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...
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:
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.
I think that since the commit 42630cbd1ffd44d146ff96f0a4be6f3c12f81d75 introduced in version 0.6.0, because of the line https://github.com/jaredhanson/passport/blob/cfdbd4a762b51e339ebfea931d65bccbbde53282/lib/sessionmanager.js#L28 , this strategy (doing req.login) does not work (without some changes) anymore since it regenerates a new session ID.
Well spotted @ctaschereau. That's a real breaking change for people using this technique. Maybe the maintainers have no idea people use the module in this way?
Anybody figured out how to update the existing req.user object?
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 callingreq.login()
with the updated values. However nowhere is this in the documentation--would love to see it there!