How to handle user loggin identifier change with Node/Express/Passport? - node.js

In my application, users sign in use their email as the unique identifier. But I am asked to implement a function which lets user change their email in the profile page after they log in. The issue is even if I can make the database successfully update, the client session stores the old information. Then if I try to do (e.g. GET) something, the req.user.email will not be in the database. How to handle such issue? Or in another word, how to update the express session correctly?

Once you sure the user data can be put in the session, you can just call req.logIn again (this function has been called by passport in the login process), and it will call the process correctly (e.g. calling the serializeUser function, etc)
req.logIn(newUser, function (err) {
if (err) {
// Ops! Something went wrong!!
}
});
Hope it can help!

Related

How to send a flash error when passport.deserializeUser() fails?

Using passport in an express app. For reasons, the session tokens expire after one hour.
If the user is active when the session expired, the deserialize function "fails", i.e., user is undefined.
passport.deserializeUser(function (id, done) {
const user = sessionManager.userLookup(id);
done(null, user);
});
The trouble is that when user is undefined, then there is no req.user for subsequent middleware. So to the code it simply appears that the user is not signed in, with no breadcrumbs to indicate that the session just expired. The app simply redirects all request from unauthenticated users to /login.
For a user in the middle of a workflow, this experience is sub-optimal.
The expiration can be detected within passport.deserializeUser() like this:
passport.deserializeUser(function (id, done) {
const user = sessionManager.userLookup(id);
const errorInfo = ( expire logic check ) ? 'session has expired' : null;
done(errorInfo, user);
});
I can get the logic check right with the sessionManager. The trouble with this solution is that passport sends the user a 500 Internal Server Error, which is also sub-optimal.
What I would like is for the app to send a flash error saying the session has expired. But passport.deserialize() has no visibility to the req object for calling req.flash().
At this point the only way I can think to resolve the issue is to insert a middleware before passport, where the code would lookup the user in the session manager and call req.flash() if the session has expired. It seems like passport should provide a better way to handle such errors.
Answers would be extra-helpful if they include a link to documentation for passport.deserialize(). The only docs I have found here make no mention of how passport handles errors or if it is possible to configure or override the behavior.
UPDATE
After some reflection, flash is not the best mechanism for reporting the session expiration. The app should instead redirect to a "session expired" page. However, the main question still stands. The call changes from req.flash() to res.redirect(), but neither of these objects is available in passport.deserialize().
you can add req as a first parameter in the function, like this:
(also, recommend you use arrow function)
passport.deserializeUser(async (req, id, done) => {
req.flash('error', {});
(...)
});

NodeJS: Force user to refresh session after permissions have been changed by another user

In my NodeJS application I use express-session for sessions, express-mysql-session to store the session to MariaDB, Passport for authentication and Sequelize for ORM.
The problem I have right now is that I do not know how to refresh a session of a user whose permissions have been changed by an admin of the application.
I tried something like req.logIn() but this refreshes only the session of the admin who is doing the permission changes.
My code looks like this:
editUser = function (req, res) {
var userData.id = req.body.id;
userdata.access = req.body.access;
models.User.update(userData, {where: {id: userData.id}})
.then(function (affectedRows) {
// User has been updated.
// Changes should be active without having the user to log out and log in again
});
);
}
Has anyone an idea how I can refresh the session of the user whose permissions have been changed by another user?
The express-mysql-session needs a table and few fields configured to store the sessions info.
One of those fields is called expires.
If you set that field to Date.now(), the user should have its session expired.
Update:
After reading your comment and looking through their code, we can see that the data stored in the session row in DB is a serialized JSON.
Since you're able to identify that user's session in the DB, you could also:
read the session's data,
JSON.parse() it,
update the .roles array (the property where you
keep user's role),
JSON.stringify() it and save it back to DB.
I have found a solution for my problem at the following thread PassportJS - Is it possible to change req.user for another user?
I updated my passport.deserializeUser() to make a db request and reload the user. The queried user is then commited to the callback. The function now looks like this:
passport.deserializeUser(function(user, done) {
models.User.findOne({where: {id: user.id}})
.then(function(updatedUser) {
done(null, updatedUser);
}, function(err) {
console.error(err);
done(null, user);
});
});

Vuejs/Node/Express Session ID workflow with OpenID

I am having a tough time understanding how I'm supposed to implement sessions.
Currently I'm writing a Vuejs app and, somehow, have managed to evade implementing any kind of Oath2 "sign in with x" process, or at least have copy and pasted my way to success without understanding, I'm afraid.
Here's what I'm trying to do, and it actually works 90% of the way there.
I've implemented passport-steam and I can click through a Sign in to Steam button, login with my account, and get kicked back to my homepage. A sessionID cookie is set. This is how I have it all configured:
app.use(session({
secret: 'topSecretKey!',
name: 'sessionID',
resave: true,
saveUninitialized: true
}))
passport.use(new SteamStrategy({
returnURL: `${host}/auth/steam/return`,
realm: host,
profile: true, // enable profile exchange
apiKey: ApiSettings.apiKey
}, (identifier, profile, done) => {
process.nextTick(() => {
// ...
// fetch the user profile in the background, matching
// the steamID to the account
profile.identifier = identifier
return done(null, profile)
})
}
))
app.get('/auth/steam/return', passport.authenticate('steam', {
failureRedirect: '/'
}), (req, res) => {
console.log('sessionID=' + req.sessionID)
console.log(`User logged in w/ Steam ID #${steamID}`)
res.redirect('/')
})
I run all of this, I sign in, and check my Node console and all is well!
Found existing account.
sessionID=2YL_YdMrauNyZ-F0gnIv3XV_5sNFo4C9
User logged in w/ Steam ID #xxxxxx
But here begins my questions. sessionID is stored in the request object, this is clear.
Is the sessionID supposed to be used as a token when querying an OpenID-enabled API? (Steam API doesn't require anything other than the Steam ID which is returned plain as day.
Is the sessionID supposed to ever make it to the frontend? Currently I static-serve a /dist directory (again, this is Vue) so Node doesn't actually do much except handle API calls out to the SteamAPI. Of course, this doesn't actually require a sessionID... just an APIkey and the users SteamID which I store in their user profile on the Mongo side.
What is the pattern here? Am I supposed to put the users' sessionID in their user record in Mongo and ... pass it around? Seems strange to me to do it that way.
What's the procedure to check if a user is logged in? Check the cookie sessionID and make a request to return that user object to the frontend (using Vuex layer for quick retrieval).
An extension of #2, but in theory, is the sessionID ever supposed to make it to the frontend? The only data manipulation is happening in my Vuex stores - for example, adding a game for a user. How is the sessionID supposed to be used?
Thanks in advance - I know these questions are all over the place but I've been pulling my hair out!
UPDATE August 2nd:
Thanks to anyone who read - I had some developments last night that were definitely very helpful in understand how this workflow is supposed to go. Couple things I learned:
The sessionID is not supposed to be referenced on the frontend - it is passed back and fourth automatically between frontend and back to Node via the request
Rather than relying on a cookie (which keeps resetting the sessionID every time Node restarts) I set up a Mongo store using connect-mongo. This way my sessionID never actually changes until it naturally expires.
As for #4, checking if a user is logged in, I am just setting a cookie called loggedIn. I honestly can't think of a better way to do this, as the sessionID cookie is HTTP only, and thus, unavailable to any frontend Javascript/Vue in this case.
I've come to the understanding that the sessionID is to be used in the backend only, but I still am wondering if I'm supposed to connect it to the user some way. This link was super helpful but I still feel as if my workflow is not perfect. When I do passport.serializeUser, what am I ideally supposed to pass to done() ? I am passing done(null, user.steamID) as the SteamAPI only requires the user SteamID to request their game list. Is this OK to do, or should I be passing the sessionID? Still confused!
UPDATE AGAIN
Great source of information here, regarding the security and the why behind all of the little details.

Passportjs extract user from isAuthenticated function

Alrigh, so I have set up an entire passport local authorization, every thing works pretty perfectly, except I can't seem to wrap my head around one issue with passport.js.
I am authorizing users through sessions (they work fine), however I can't seem to extract the user info from that session, although if I manually check my session db, for every session there is a userid (which links to the user).
Also the deserialize functions seem to return the user and the log is showing the correct user.
Now the problem is that I can't seem to pass this user<->session info anywhere in my routes.
The issue that I seem to have with this is that people that are logged in, are authorized, but they can fiddle with the request body as much as they like, doing things as posting messages in their name, logging them out, ...
Code example:
router.post('/games/:id/verify', isAuthenticated, function(req, res){
log('#POST verify for game with id: ' + req.params.id);
gameController.postVerify(req.params.id, req, res);
});
In this code, it just checks if a user is logged in, if possible, I want to check which user is logged in to that session, so that he cannot verify this game if he was not part of it.
Let's say user A, B were part of the game and need to verify it. This function allows user C who is logged in to verify for them, because this function only checks if the user is logged in.
If I can pass the logged in user (user C in the test case) to the gameController, I can write my own checks in there and throw them out.
Passport will populate req.user when authentication has succeeded. It will be populated by the data provided by deserializeUser, which generally is a database record of the user.
You can use that information to limit other database queries/checks/etc to make sure that the logged-in user is allowed to access particular information.

Creating Users with Passport

I'm using Node.js and intend to use Passport for authentication. However, all of the examples I see online assume the user has already been created. I'd like to know how to create a user's session correctly, after they are saved in the database (I have this part working), so that Passport will recognize them. (I do not want to save the new user and then force them to go to a login page.)
Just call:
// user is the user instance you just registered and created
req.logIn(user, function(err) {
if (err) return next(err);
// login success!
res.redirect('/home'); // or whereever
});
Documentation for this function is in the code (I need to add it to the guide):
https://github.com/jaredhanson/passport/blob/master/lib/http/request.js

Resources