Persistent user sessions in node.js using Google Authentication - node.js

I followed a tutorial to include Google authentication for my web application using the passport-google-oauth module. The server.js file has the following lines of code:
app.use(express.session({ secret: 'victoriassecret' })); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
In addition, I find that the application automatically logs the user out after some time. Passport is configured in a separate file that is imported in server.js. Is there some way I can increase the time before the user is logged out, or even better, not log him out until he clicks on the logout button? Also, what is the session secret?

1) You can define the maximum life-time of a session cookie ( and concurrently the time before a user is automatically logged out ) using the maxAge option of the cookie parameter like this :
app.use(expressSession({ cookie: {maxAge: 10000} , secret: 'victoriassecret'}));
According to this maxAge value ( 10000 ) the cookie's maximum life-time will be 10.000ms(10 sec).
(obviously you need a much bigger value than this)
Thus,you can increase the maxAge value in order to suit your needs and make sure user does not get logged out until he decides so, pressing the Logout button.
2) The session secret is a random string used to hash the session with HMAC ( more on HMAC : here) in order to protect the session from being highjacked.

Related

Store cookies only for users who agreed to terms

To comply with GDPR without an annoying cookie banner, I would like the public pages on my website to not store cookies. To facilitate the journey of registered users, I would like to use flash messages. The problem is that these two seem incompatible, as setting sessions:
const session = require('express-session');
// Initialize mongodb session storage to remember users.
const store = new MongoDBStore({
uri: config.mongoUri,
// The 'expires' option specifies how long after the last time this session was used should the session be deleted.
// Effectively this logs out inactive users without really notifying the user. The next time they attempt to
// perform an authenticated action they will get an error. This is currently set to 3 months (in milliseconds).
expires: max_session_ms,
});
// Enable sessions using encrypted cookies
app.use(
session({
cookie: {
// Specifies how long the user's browser should keep their cookie, probably should match session expiration.
maxAge: max_session_ms,
sameSite: "strict",
},
store: store,
secret: config.secret,
signed: true,
resave: false, // Unknown effect. See https://github.com/expressjs/session#resave
saveUninitialized: false, // Save only explicitly, e.g. when logging in.
httpOnly: true, // Don't let browser javascript access cookies.
secure: config.secureCookies, // Only use cookies over https in production.
})
);
and flash messages with:
const flash = require("express-flash"); // Disabled to avoid cookies.
app.use(flash());
stores a cookie in the browser with an empty flash message for public pages. I don't think server-side only flash messages exist (see Server-side only flash messages (no cookies) ). I tried setting res.locals but it gets erased between redirects.
Is it possible to store cookies only for visitors who agreed to the terms in the logged-in area of the website and not for visitors in general?
3 remarks about your question :
requesting consent is not based on GDPR, it's requested by EU directive 2002/58/CE
this directive states explicitly that you don't need to request consent if you are storing a session on user's browser
storing a cookie or a flash is the same from EU directive's point of view, you can't avoid requesting consent in the case it's required by replacing cookies by anything that writes data in user's browser
if the rules are not clear for you, check the CNIL's page (French data authority protection) for example : https://www.cnil.fr/fr/nouvelles-regles-cookies-et-autres-traceurs-bilan-accompagnement-cnil-actions-a-venir
EDIT :
For everyone, to clarify when consent is needed, it's best you read the text of the directive directly it's very clear (article 5.3 directive ePrivacy 2002/58) :
Member States shall ensure that the use of electronic communications networks to store information or to gain access to
information stored in the terminal equipment of a subscriber or user
is only allowed on condition that the subscriber or user concerned is
provided with clear and comprehensive information (...) about the purposes of the processing,
and is offered the right to refuse such processing by the data
controller. This shall not prevent any technical storage or access for
the sole purpose of carrying out or facilitating the transmission of a
communication over an electronic communications network, or as
strictly necessary in order to provide an information society service
explicitly requested by the subscriber or user.

Why use cookie-session in addition to passport.js?

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 }))

is it reasonable to get sessionid from url other than cookie? something about express-session

I have some domain( maybe change very frequently), and two stable domain(e.g. auth.aaa.com, api.aaa.com).
Since express-session(https://www.npmjs.com/package/express-session) default get sessionid from cookie, but when crossing domain,ajax won't send cookie( I don't want to use something like Access-Control-Allow-Credentials ).
I want to add the sessionid to the querystring, and forge a cookie before express-session middleware.
app.use(function(req,res,next){
var ss = req.query.ss;
if(ss){
var signature = require('cookie-signature');
var cookie = require('cookie');
var signed = 's:' + signature.sign(ss, "secret");
var data = cookie.serialize('jsessionids', signed);
req.headers.cookie = data;
}
next();
})
app.use(session({
name:'jsessionids',
store: new redisStore({
host:config.redis.host,
port:config.redis.port,
pass : config.redis.password,
db: config.redis.database
}),
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
secret: 'secret'
}));
is it reasonable? or any suggestion else?
It is generally not advisable to add the session as a query parameter, you have to jump through lots of hoops to get them to near the same level of security as cookies.
The main problem is that it is much more vulnerable to session fixation or session hijacking, which is where an attacker can steal and use another user's session.
Some key points to take into consideration
Query parameters are stored in browser history, bookmarks and referrer headers (just to name a few) which
could allow an attacker to use another users session on a shared
environment. Query string based sessions are much easier to leak outside their intended scope.
Cookies have better security mechanisms built in such as the
httpOnly flag which makes the cookies in-accessible to JavaScript
(whereas query strings are always accessible). The secure flag makes
sure that cookies are only sent over a secure connection (You could
perhaps use HSTS to help guard against MITM attacks for query string).
A user who share a link with their sessionID in the query string
which would allow any other user to assume their identity.
If you do decide to use the sessionID in the query string make sure you set an expiration time for the session and always to use TLS to securely transmit the session (same applies to any authentiction method).
Saying that, If you can avoid using query string based sessions, I would advise you do.

Node+Passport.js + Sessions + multiple servers

Passport is great. I now discovered that I have some problem with how it handles sessions.
I must be using it wrong.
All works well for me with login + sessions + user data I store in my database.
However I find that when I move to production environment (cloud on EC2 with multiple servers), I lose the login session each time.
This is now clear to me - probably happens since the session is unique to each server.
So my question is - how do I get around this..
I guess I will need to store my own cookie on the user's browser?
Does this mean that I cannot use express.session at all?
Thanks,
Ilan
OK,
So basically what I was looking for (not sure it would be the same answer for everyone else) was a way to store session data between loadbalanced instances without making a DB call for every page view, which seems excessive to me, since I just need to keep the user signed in to Google/FB.
It seems that the answer I was looking for was the cookie-session middleware
https://github.com/expressjs/cookie-session
This needs to replace the default express.session mechanism which uses MemoryStore. BTW MemoryStore itself gives you a warning when run that it will not scale past a single process, and also that it may cause a memory leak.
Which if I understand correctly is serializing the session data itself into the session cookie (encrypted) instead of just using a session ID in the session cookie.
This seems perfect to me. Obviously I don't expect it to work if you have a lot of session data, since a cookie is limited in size. In my case, I just needed the name, ID and avatar url, so I think this will suffice.
Thanks for everyone who helped.
You need to store your session data in a 'global' area, that is accessible to all your servers. This could be redis or another DB.
Take the example from MEAN.JS. Here they use express-session with a MongoDB storage container (since they are a MEAN stack ; ), via connect-mongo. Their project is super easy to set up, if just for an example.
Code while setting up express is like this:
//top of file
var session = require( 'express-session' )
mongoStore = require( 'connect-mongo' )( {
session: session
} );
//...later in setup
// Express MongoDB session storage
app.use( session( {
saveUninitialized: true,
resave: true,
secret: config.sessionSecret,
store: new mongoStore( {
db: db.connection.db,
collection: config.sessionCollection
} )
} ) );
// use passport session
app.use( passport.initialize() );
app.use( passport.session() );

ExpressJS creates a new session on each page change

I'm trying to implement express and passport sessions like so:
app.use(connect.bodyParser());
app.use(express.cookieParser());
app.use(express.session({
cookie: {
path: "/",
httpOnly: true,
maxAge: null
},
store: redisStoreConnect,
secret: "something",
key: 'pksy.sid'
}));
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser (user, done) ->
done null, user.email
return
passport.deserializeUser (email, done) ->
User.findOne
email: email
, (err, user) ->
done err, user
return
return
If I navigate to a page in my site, a new session is created and stored in redis. If I refresh that page, the session appears to persist. If I navigate to a new page or even if I close the tab and reopen to the same page, a new session is created.
This is especially frustrating since passport only authenticates the session that was generated for my login page. How do I get my sessions to persist across pages?
Update: Thanks #robertklep for reminding me to check what cookie the browser was actually sent back (which I should have done to start). It turns out the browser is sending back the right cookie, and the authentication is working. The sessions are in fact persisting, but for some reason a new session (unused by the browser) get's created with each page request. How can I stop this?
"Oh, you didn't know the browser doesn't send the session cookie with the request for the favicon?" says my roomate the hacker, whose ilk created the need for the following single line of code.
11 hours of debugging later here is what solved the problem:
app.use express.favicon()
Express was treating the favicon like any other resource, while the browser was making a specialized, apparently cookieless request for it. Express assumed this request must have come from a client without a session cookie, so it created a new session for them; a session never to be called upon again. express.favicon() saves the day!
In my case I have to use
app.use(passport.initialize());
app.use(passport.session());
before
app.use(app.router);
Hope this can help.

Resources