Express session store and reapInterval - session

I have a question about express session store memory with setting the reapInterval value. I have an example code, which will output the values of a memorystore every 5 seconds. If i now set a reapinterval of 5000, it should clean up expired session every 5 seconds right? So my example looks like this:
/**
* Module dependencies.
*/
var express = require('express');
var app = module.exports = express.createServer();
var MemStore = require('express/node_modules/connect/lib/middleware/session/memory');
var store = new MemStore({reapInterval: 5000});
// Configuration
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.session({secret: 'your secret here', store: store, cookie: {maxAge: 30000}}));
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
setInterval(function(){
console.log(new Date());
console.log(store);
}, 5000);
app.configure('development', function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
app.use(express.errorHandler());
});
// Routes
app.get('/', function(req, res){
res.render('index', {
title: 'Express'
});
});
app.listen(3000);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
And now the problem is, that if I revisit the page after 30 seconds, I get a new SID, but the old session in memorystore is still there... should it not be checked every 5 seconds and deleted?
Thx for your help!

So the first problem is a misunderstanding here. reapInterval does not do anything. MemoryStore clears cookies based on the expire time of session cookie. So there is in fact a bug in Connects MemoryStore. The way I see it the broken flow goes like this.
Set cookie to expire in X.
Get session data, has X gone by? No, ok.
(cookie expires)
Get session data, session doesn't exist, generate new one.
X has gone by but the session ID is missing because the browser expired it already.
There is a discussion regarding this here.
https://github.com/senchalabs/connect/issues/328
And a juicy quote
"short cookie sessions would be screwed I guess" --visionmedia

Related

Ajax post success function not executing after response from express to set cookie

I'm attempting to store a session cookie after a successful firebase-authentication login. All the firebase functions appear to be executing correctly. I'm able to retrieve my idToken after logging. The Post correctly sends this idToken to the server where it is validated. However, I'm getting stuck on the last step which is to store the session cookie in my success function, which doesn't execute.
To troubleshoot this I go to the Chrome Dev Tools and access the network tab and turn on preserve log. Here I can see my post request. When I click on response it says "Failed to load response data". This at least tells me that the post is partially working. The client is at a minimum sending the idToken, but I can't verify that the response is being sent from the server. Additionally, when I navigate to the Application tab of the chrome dev tools and click refresh I don't see my session cookie there.
Here is the client side code:
btnLogin.addEventListener('click', e => {
// Get eamil and pass
// TODO: Check for real email
const email = txtEmail.value;
const pass = txtPassword.value;
// As httpOnly cookies are to be used, do not persist any state client side.
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
// Sign in
firebase.auth().signInWithEmailAndPassword(email,pass).then((userCredential) => {
// Get the user's ID token as it is needed to exchange for a session cookie.
userCredential.user.getIdToken().then((idToken) => {
// Session login endpoint is queried and the session cookie is set.
// TODO: Add CSRF protection);
$.ajax({
type: 'post',
//accepts: 'json',
data: {idToken: idToken},
//dataType: 'json',
contentType: 'application/x-www-form-urlencoded',
success: function(data) {
console.log('MADE IT TO THE SUCCESS FUNCTION');
//document.cookie = data;
firebase.auth().signOut();
$('#loginForm').before(' \
<div class="alert alert-secondary" role="alert"> \
The login was succesful! \
</div> \
');
//setTimeout(window.location.assign('/admin-manage'), 5000);
},
error: function() {
console.log('post error');
$('#loginForm').before(' \
<div class="alert alert-danager" role="alert"> \
The idToken post failed! \
</div> \
');
},
timeout: 5000
});
});
});
});
Here is the server side code:
/* POST FOR SESSION INITIALIZATION */
app.post('/admin-login', (req, res) => {
// Get the ID token passed and the CSRF token.
var idToken = req.body.idToken.toString();
// TODO: CSRF TOKEN
// Set session expiration to 2 days.
const expiresIn = 60 * 60 * 24 * 2 * 1000;
// Create the session cookie. This will also verify the ID token in the process.
// The session cookie will have the same claims as the ID token.
// We could also choose to enforce that the ID token auth_time is recent.
adminApp.auth().verifyIdToken(idToken).then((decodedIdToken) => {
console.log('ID TOKEN: ' + idToken);
// Only process if the user just signed in the last 5 minutes
if (new Date().getTime() / 1000 - decodedIdToken.auth_time < 5 * 60) {
// Create session cookie and store it serverside
return adminApp.auth().createSessionCookie(idToken, {expiresIn});
}
else {
throw new error('The IDToken has expired');
}
})
// eslint-disable-next-line promise/always-return
.then((sessionCookie) => {
console.log(sessionCookie);
//Set session cookie on client side
const options = {maxAge: expiresIn, httpOnly: false, secure: false};
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({status: 'success'}));
})
.catch((error) => {
console.log(error);
res.status(401).send();
});
});
EDIT
Here is an image of the network view from me running chrom dev tools.
CHROME DEV TOOLS APPLICATION VIEW
As you can see the post script gets canceled.
Here is a detailed view of the request headers:
enter image description here
EDIT #2
I've made so more changes to the server-side code and still haven't seen any change in results. Here is the new server-side code:
/* POST FOR SESSION INITIALIZATION */
app.post('/admin-login', (req, res, next) => {
// Get the ID token passed and the CSRF token.
var idToken = req.body.idToken.toString();
// TODO: CSRF TOKEN
// Set session expiration to 2 days.
const expiresIn = 60 * 60 * 24 * 2 * 1000;
// Create the session cookie. This will also verify the ID token in the process.
// The session cookie will have the same claims as the ID token.
// We could also choose to enforce that the ID token auth_time is recent.
adminApp.auth().verifyIdToken(idToken).then((decodedClaims) => {
console.log('DECODED CLAIMS AUD: ' + decodedClaims.uid );
// Only process if the user just signed in the last 5 minutes
if (new Date().getTime() / 1000 - decodedClaims.auth_time < 5 * 60) {
// Create session cookie and store it serverside
// eslint-disable-next-line promise/no-nesting
adminApp.auth().createSessionCookie(idToken, {expiresIn}).then((sessionCookie) => {
console.log(sessionCookie);
//Set session cookie on client side
const options = {
maxAge: expiresIn,
httpOnly: true,
secure: false
};
res.set('Content-Type', 'application/json');
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({status: 'success'}));
});
}
else {
throw new error('The IDToken has expired');
}
}).catch((error) => {
console.log(error);
res.status(401).send('UNAUTHROIZED REQUEST!');
});
});
I also took a wireshark capture on my loopback adapter so that I could capture the localhost traffic. I can see the HTTP response with the set-cookie tag, but not sure what the problem is. See the image below:
HTTP Post Response Wireshark Capture
EDIT #3
I've finally made a little progress. I noticed the URI in my wireshark capture wasn't "samesite" so I added the "sameSite: 'None'" option to my set-cookie options.
So my new options are:
const options = {
maxAge: expiresIn,
httpOnly: true,
secure: false,
sameSite: 'None'
};
This fixed the issue on my localhost development environment. However, I changed the secure option to true then did a firebase deploy. After the deployment I logged into the production environment and found that the cookie would not generate. So the only question left is why it won't work in the production environment.
EDIT #4
Alright, I'm losing my mind. I changed the options back to how I had them in EDIT #3 so that I could continue working the app, and the session cookie is no longer being set again. I'm at a complete loss as to why it won't work now. I even went into chrome to clear all browsing history in hopes this would resolve the issue, but no luck. If anyone can shed light on what is happening here I would greatly appreciate it.

In Electron, is the default session persistent?

I'm using Electron (v1.2.7) and I need session cookies to persist between app restarts (for auth).
Is it possible? I know in Chrome when you set "Continue where you left off", the session is kept but not sure if this works the same in Electron.
As a workaround, I tried storing the session cookies also as non session cookies but it failed with a generic error.
Clarification: I'm not setting the session cookies they are set by other webpages during authentication.
The default session is persistent, but if you set cookies using session.defaultSession.cookies.set() you must set the expiration date in order for the cookie to be persisted.
You can persist cookies setting the session and a expirationDate
This example was made on Angularjs
var session = require('electron').remote.session;
var ses = session.fromPartition('persist:name');
this.put = function (data, name) {
var expiration = new Date();
var hour = expiration.getHours();
hour = hour + 6;
expiration.setHours(hour);
ses.cookies.set({
url: BaseURL,
name: name,
value: data,
session: true,
expirationDate: expiration.getTime()
}, function (error) {
/*console.log(error);*/
});
};
PD: A problem that i had was if i didn't persist them, after a fews reloads my cookies get lost. So in this example i'd persist them 6 hours
If you are loading an external webpage, you should be using Electrons <webview> tag to disable nodeintegration and for other security reasons. Using a webview will give you easy access to the Partition attribute which can be set to persist (ex: persist:mypage). You can also set the partition attribute on an Electron window if needed.
I was loading an external webpage and the configuration below worked for me. By default the webpage is configured to use "session cookie" and thats why I change it to "persistent cookie" with expiration date of 2 weeks:
// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
const path = require('path')
const util = require('util')
function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 700,
height: 500,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
partition: 'persist:infragistics'
},
icon: __dirname + '/assets/favicon.ico',
show:false
})
let cookies = mainWindow.webContents.session.cookies;
cookies.on('changed', function(event, cookie, cause, removed) {
if (cookie.session && !removed) {
let url = util.format('%s://%s%s', (!cookie.httpOnly && cookie.secure) ? 'https' : 'http', cookie.domain, cookie.path);
console.log('url', url);
cookies.set({
url: url,
name: cookie.name,
value: cookie.value,
domain: cookie.domain,
path: cookie.path,
secure: cookie.secure,
httpOnly: cookie.httpOnly,
expirationDate: new Date().setDate(new Date().getDate() + 14)
}, function(err) {
if (err) {
log.error('Error trying to persist cookie', err, cookie);
}
});
}
});
Note: Its important to ensure that you've set the "partition" webPreferences property as well.
A String that sets the session used by the page. If partition starts with persist:, the page will use a persistent session available to all pages in the app with the same partition. if there is no persist: prefix, the page will use an in-memory session
Origin source.

Parse express server side login using express-session

I'm using parse on node. I have an express app, and a JS browser app, that is hosted off the express server.
At the moment the app has it's own login. It logs the user in on the client, and the client remains logged in.
I want to be able to log the client in via an express route /login. When they log in via this route, i want to log them in on the client side.
I have poured over documentation on this but I have struggled to find any real examples of how this is all done.
Here is some code i have found:
var cookieSession = require('cookie-session'),
// I added this require as it seems the code is using it;
session = require('express-session');
app.use(cookieSession({
name: COOKIE_NAME,
secret: "SECRET_SIGNING_KEY",
maxAge: 15724800000
}));
//
// This will add req.user if they are logged in;
//
app.use(function (req, res, next) {
Parse.Cloud.httpRequest({
url: 'http://localhost:1337/parse/users/me',
headers: {
'X-Parse-Application-Id': 'myAppId',
'X-Parse-REST-API-Key': 'myRestAPIKey',
'X-Parse-Session-Token': req.session.token
}
}).then(function (userData) {
req.user = Parse.Object.fromJSON(userData.data);
next();
}).then(null, function () {
return res.redirect('/login');
});
});
//
// login route;
//
app.post('/login', function(req, res) {
Parse.User.logIn(req.body.username, req.body.password).then(function(user) {
req.session.user = user;
req.session.token = user.getSessionToken();
res.redirect('/');
}, function(error) {
req.session = null;
res.render('login', { flash: error.message });
});
});
//
// and logout.
//
app.post('/logout', function(req, res) {
req.session = null;
res.redirect('/');
});
This looks pretty good, but this won't add a session on the client? How do parse the server login down to the client; Do i pass the session Token and use it on the client?
//
// If i call this code in the browser, i want the logged in user;
//
var current_user = Parse.User.current();
I have been unable to find any real code on-line that demonstrates all of this in the best-practice manner.
Is this the 'best practice' known solution or is there a better way of doing this?

Redis Not Deleting Session Keys

I am using Redis to store my session data for my ExpressJS application and in the past have run into some issues where the persistent cookie is keeping my user logged in causing issues with development. I have tried to clear my session data with the redis-cli, but despite running DEL KEYS * and being given the (integer) 0 response, I still see the sessions appear when I run KEYS *. Can anyone help me with removing that data?
Example:
127.0.0.1:6379> KEYS *
1) "sess:O7pchKqe-n7NUhP3lBANaf7LMjJG0U0a"
2) "sess:tSyQCCISPBpH88zT3MJjHw2tidttMdRs"
127.0.0.1:6379> DEL KEYS *
(integer) 0
127.0.0.1:6379> KEYS *
1) "sess:O7pchKqe-n7NUhP3lBANaf7LMjJG0U0a"
2) "sess:tSyQCCISPBpH88zT3MJjHw2tidttMdRs"
ExpressJS code storing the session data:
var express = require('express');
var app = express();
var passport = require('passport');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var RedisStore = require('connect-redis')(session);
var path = require('path');
//Port Setting
var port = process.env.PORT || 3000;
//Set Morgan as Logger
app.use(morgan('dev'));
//Extract POST Data
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
//Session Cookie
app.use(cookieParser());
app.use(session({
store: new RedisStore({
host: '127.0.0.1',
port: 6379
}),
secret: 'super-secret',
resave: true,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: false //turn to true on production once https is in place
}
}));
(transferring from comment)
I think you want flushdb to delete all keys. del doesn't have a way to specify a wildcard, so del keys * will try to delete two keys: one called "keys" and one called "*". The response 0 means that 0 keys were deleted.

heroku http streaming (sse) disconnect not detected with express.js

Heroku is not detecting when a client disconnects.
I cloned their starter app and added a couple lines for sse:
var sse = require('connect-sse')();
app.get('/testing', sse, function(req, res, next) {
var interval;
interval = setInterval(function() {
var date;
date = new Date();
res.json({
ping: date
});
return console.log(date);
}, 2000);
return req.on('close', function() {
clearInterval(interval);
return console.log("cleared");
});
});
When I go to my app I see the correct reponse:
id: 0
data: {"ping":"2014-01-05T23:53:49.835Z"}
id: 1
data: {"ping":"2014-01-05T23:53:51.839Z"}
But after I close the browser tab heroku does not register that I did.
With heroku logs -t I keep getting the date printed out and never cleared
This works locally with foreman start but not on heroku.
Anyone know what I am doing wrong?
Based on the documentation for the connect-sse node module, it looks like you should be referring to the sse middleware without parentheses, like so:
app.get('/testing', sse, function(req, res, next) {
// ...
});

Resources