Okay, so I am just getting in to the MEAN stack, and I'm trying to build an app with Passport.js.
I'm just starting user serialization to maintain sessions. In their example, Passport uses this for serialization and deserialization:
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
So, my question is this: is this example considered secure? If I understand this right, doesn't that mean that a client could just fake the user ID to become logged in as whichever user has that ID?
I guess what I'm asking is, is their example considered "secure" and a proper way of doing things, or is it expected that you will change these functions to generate unique serialization. If this is considered secure, then I think I'm missing something on how this works, and I'd love to be filled in.
On the other hand, if this is not secure and I am expected to write my own functions in place of these, would the following be a valid and secure way of doing this:
Upon serialization of a user, generate a random hash and put that in the user's database entry. Random hash is the serial that represents that user.
Upon deserialization, look up the random hash in the database and return the corresponding user. If the hash isn't found throw some kind of error.
When the user logs out, delete their serial hash from their entry in the database.
If my logic up until here is valid, what would be a proper way to generate this random hash?
Yes, that is how you do serialization / deserialization. The id is not received from the client.
Session information is stored to your local session store, eg. database, under a random ID. For example, express-session uses uid-safe to generate the session ID. This ID is set to a cookie which is then sent to the client.
When the client makes a request, the session ID is read from the cookie if it has not been tampered (usually the ID is signed with the secret you define when intializing sessions). Using this ID, the real session data is read from your local session store. This is where the id used in deserialization comes from.
Here is an example what a session object stored to MongoDB could look like:
{
"_id" : "_RXnIfFeb_qH6AXMO2ounrxlJZPHkwda",
"session" : "{\"cookie\":{\"originalMaxAge\":null,\"expires\":null,\"secure\":false,\"httpOnly\":true,\"path\":\"/\"},\"passport\":{\"user\":\"5614c62e4372842244660dcf\"}}"
}
The _id here is what is signed and sent in the cookie. The session string, decoded to JSON object, is:
{
"cookie": {
"originalMaxAge": null,
"expires": null,
"secure": false,
"httpOnly": true,
"path": "/"
},
"passport": {
"user": "5614c62e4372842244660dcf"
}
}
Here, passport.user is the actual user ID returned by my applications seralizeUser that is given to deserializeUser when a session is being loaded.
So what happens if you change the cookie content? If the cookie is signed, it will be invalidated because the altered value does not match the signature. If it is not signed, the value is used when querying session store. The query will not return anything because you changed the ID and there is no matching session in the database (unless you have found out / guessed the session ID of another active session - ie. performing session hijacking).
Related
My understanding of passport.js so far is that passport.js serializes the user object and sends an ID every time to the client. I am just starting with it so sorry if it's a silly question:
Instead of express-session, I am using cookie-session as I am a beginner. My understanding of cookie-session is that it sends a session ID every time, and this ID can be used to look up the database when needed.
Now, I don't understand why we can't just use the passport.js ID? Why do we need to use cookie-session in addition? Also, (this may be a little unrelated, but) is the difference between session-based authentication and token-based authentication that this ID that's shared is dynamic, or changing every time? Is this still the standard and modern way of doing it in 2020?
"Instead of express-session, I am using cookie-session as I am a beginner."
using cookie session does not make anyone beginner. If you are going to store large data, use express-session, cause it stores the data in the database or redis, keeps the database id of that data, so when it gets a request, fetch the database with that id and compare the request credentials. On the other hand, cookie-session stores the data upto 4kb in the cookie on the user browser and since only user-id is stored in the cookie with passport.js, generally cookie session is used.
passport.serializeUser(
(user, done ) => {
done(null, user.id); // stores the id<4kb
}
);
When client authorizes your app, google send the responds to your callback url.
app.get("/auth/google/callback", passport.authenticate("google"))
passport.authenticate() will call req.login() this is where passport.user gets generated. req.login() will initiate the serializeUser() which determines which data of the user should be stored in the session.
passport:{user:userId}
Now this object will be assigned to req.session. so we will have req.session.passport.user
Everytime when you make a request to a server, browser automatically checks if there is cookie set related to that server and if there is it automatically attaches the cookie to the request. If you were using token based authentication, you had to manually attach the cookie to request everytime you make a request. Cookie is just transportation medium, you store data and move the data, you can even store the token in cookie. Cookie is not just related to authentication. If you have server-side project, you have to set cookie.(this is just a side node).
"My understanding of cookie-session is that it sends a session ID every time, and this ID can be used to look up the database when needed."
so far I explained how session is created. Now what happens when user makes a request?. In app.js file you should have two middleares.
app.use(passport.initialize());
app.use(passport.session());
app.use(passport.initialize()) this function checks if req.session.passport.user exists, if it does it will call passport.session(). if it finds a serialized user object in the session, it will consider this req is authenticated. And then deserializeUser() will be invoked. it will retrieve the user and attach it to req.user
You don't need to use session. It is totally upto you. Just put {session: false} in route. You don't need to write passport.serializeUser and passport.deserializeUser.
cookie-session puts cookie on client system, and it is sent each time with request. passportjs search that cookie and run deserializeUser to convert it into object and attach it with request object.
express-session stores session data on the server; it only saves the session identifier in the cookie, not session data.
where as cookie-session is basically used for lightweight session applications. it allows you to store the session data in a cookie but within the client [browser]. Only use it when session data is relatively small and easily encoded as primitive values See this question for more understanding
const express = require('express');
const { Router } = express;
const router = new Router();
router
.get('/', passport.authenticate('google', { session: false }))
I am working on setting up logins with session using Express, express-session, and passport.
In a passport documentation as I go I see this example: http://www.passportjs.org/docs/profile/
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
Also, I was following this tutorial: https://github.com/bradtraversy/node_passport_login
Now according to these examples deserializeUser stores the user information in req.user. However, I see that the whole user is stored which puzzles me, as this also stores the passwords to the object.
Isn't that risky?
Or maybe it is not possible to access req.user from front end?
Assuming your backend server is not compromised, req.user will stay only in your backend, which should be a trusted environment, and will not be sent back to your client via res by default.
Also, anything stored within req will only be available in the request itself, another request will have its own req instance, so data is not shared and should not leak to another request unless purposely made to do so.
However, you should always be staying on the ball, keep in mind to test and make sure all data sent back to your client does not have any sensitive info contained within them (e.g. password, tokens).
If you are not comfortable with storing that in req.user, you can always add a layer of middleware to strip the sensitive info before reaching your controller. This way, routes that use the middleware will not have sensitive info exposed.
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);
});
});
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.
In my login module once I log in I pass my request to a header to store a session here is the code
var series = rand.generate(16);
var token = rand.generate(16);
var loginToken = new LoginTokens({
userId: req.user._id,
series: series,
token: token
});
loginToken.save(function(err, l) {
if (err) {
console.log(err);
} else {
console.log(l);
res.cookie('loginToken', JSON.stringify({
series: series,
token: passwordHash.generate(token)
}));
res.status(200);
res.set('Content-Type', 'application/json');
res.end(JSON.stringify({
'success': req.params.res
}));
}
});
Though this code was pre-written and I don't understand it much(I don't get the point of randomly generating 16 digit number and saving it if somebody does please explain)
I have been tasked with implementing log out and As I don't understand the rationale behind above code I want to implement my own session module such that even if the same user logs in from a different device, both sessions will be maintained and logging out from one device will not log out someone from all device.
So now the point comes that the session id will not be based on user id. Also there is a module called express-session which I don't know if I should use or not, personally I feel that after reading it up on GitHub that what it will do is create a session id for me, which again I will have to save in my database, so why to import a module, instead I will base64 encode a random number like above and use it.
So what I want to ask is the real question
How do you implement sessions in node js?
My understanding of life cycle
A user signs up you see if he/she exists in the database and if he does you raise an error message and if he/she doesn't you create a username for him, probably by email id.
You then via code log him in. In that process, you first check if he is there in the database. If he is you go ahead take the _id field and try and create a session object. Ques what does your session table should look like? What all parameters should be present in sessions table? Then how do save them into res.cookies? Then what do you do with all those cookies, like on client side?
While implementing logout. My thinking is using passport.logout will remove the user object from request header but, I will have to first remove the entry from sessions table somehow empty the cookies and then do req.logout?
Can somebody please answer the above doubts?
I asked a very specific question but realised after the comment that what I need is logical clarity and I did not find one on youtube videos nor on SO posts, so if someone can share their views mostly in code, with some explanation it would be great.
Cookies get deleted by sending a new cookie with the same name (some invalid data would be good) and an expiry in the past.
passport.logout() removes the session cookie that passport uses to maintain the current user, the next request will no longer have the cookie and will appear as an invalid request.
I'm not sure why there's a loginToken as well as passport authentication, apart from allowing you to invalidate sessions server side (by deleting the appropriate token from the database) Check if your passport strategy configuration has any checks against LoginToken during it's deserialize user step (this is where passport takes the session cookie and turns it into a user against the request)