jaredhanson / passport

Simple, unobtrusive authentication for Node.js.
https://www.passportjs.org?utm_source=github&utm_medium=referral&utm_campaign=passport&utm_content=about
MIT License
22.86k stars 1.24k forks source link

Document req.login for refreshing req.user #208

Open aarsilv opened 10 years ago

aarsilv commented 10 years ago

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!

chichilatte commented 10 years ago

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

ghost commented 10 years ago

:+1:

homeyer commented 9 years ago

:+1:

EragonJ commented 9 years ago

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

samhuntsocial commented 9 years ago

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

adamterlson commented 9 years ago

:+1:

narendra-agashe commented 9 years ago

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

zhiyelee commented 9 years ago

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

samhuntsocial commented 9 years ago

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]

samhuntsocial commented 9 years ago

@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.

zhiyelee commented 9 years ago

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

chichilatte commented 9 years ago

@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!

samhuntsocial commented 9 years ago

@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

chichilatte commented 9 years ago

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?).

samhuntsocial commented 9 years ago

@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.

safinn commented 8 years ago

@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.

jayrc7 commented 3 years ago

@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.

ctaschereau commented 1 year ago

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.

chichilatte commented 1 year ago

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?

prionkor commented 8 months ago

Anybody figured out how to update the existing req.user object?