I'm using redis for storing my sessions in expressjs. It works fine when running locally and even works most of the time when deployed on heroku. The problem is that regularly (when deployed on heroku) I see my session updates being lost.
e.g. a user logs in to my website, and I add their user object to the session:
req.session.user = user;
however (sometimes) when I try and retrieve the object moments later (in a different request) it isn't there
//sometimes this is empty, even though I've just set it
var currentUser = req.session.user;
I initialise the session store as follows
if (process.env.REDISTOGO_URL) {
console.log('Connecting to redis:' + process.env.REDISTOGO_URL);
var rtg = require('url').parse(process.env.REDISTOGO_URL);
var redis = require('redis').createClient(rtg.port, rtg.hostname);
redis.auth(rtg.auth.split(':')[1]);
} else {
console.log('Connecting to local redis');
var redis = require('redis').createClient();
}
//sessions
app.use(express.cookieParser('secret key for cookie monster')); //used by session
var RedisStore = require('connect-redis')(express);
app.use(express.session({
store: new RedisStore({
client: redis
})
}));
Any ideas? Do I need to do some kind of flushing (or explicit saving) of the session data?
I'm going to hijack this ancient question and let you guys in on how I solved the problem.
TL;DR
You have probably copied the REDIS_URL value from your Heroku app config to your local .env file some time ago. Heroku changes that value over time and that has now actually happened.
The solution is to copy the new REDIS_URL value from your Heroku app config to your local .env file or use another Redis instance for local development.
What you probably already tried
Probably your sessions were working a few days ago and you tried and verified all the usual suspects in order to fix this sudden problem.
cookie: { secure: false }
'trust proxy'
credentials: 'include' + Access-Control-Allow-Credentials header set to true
All to no avail. Finally, when you remove the RedisStore bit from the session config, sessions are working again.
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
// ...
server.set('trust proxy', 1);
server.use(
session({
// store: new RedisStore({
// url: process.env.REDIS_URL,
// }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
path: '/',
httpOnly: true,
secure: false,
maxAge: null,
},
})
);
What's happening?
Sometime later I understood how removing the Redis store solved the problem. I was using a REDIS_URL in my local .env file that was provided by my Heroku app config. And I learned that the REDIS_URL on the Heroku app may change over time.
In order for Heroku to manage this add-on for you and respond to a
variety of operational situations, the REDIS config vars may change at
any time. Relying on the config var outside of your Heroku app may
result in you having to re-copy the value if it changes.
(emphasis mine)
So in my case that had actually happened and my local .env file still had the old REDIS_URL value. In effect, the session store could not be created, because it tried connecting to a service that isn't there. That's why removing RedisStore and thus falling back to the default MemoryStore fixed the problem.
Prevent reoccurrence
I created an account at redislabs.com and created a free Redis instance. That url I now use in my local .env file. That url doesn't change (I hope).
Related
For authentification I'm trying to understand, how sessions work. With help of documentation of express session and Sessions in Node JS I got it work.
Now I'm figuring out, what to do, that users can log out. In the documentation of express session is to read "The default server-side session storage, MemoryStore, is purposely not designed for a production environment." They recommand a compatible session store.
I have choosen connect-redis. They call it an "in-memory data structure store". Now I'm wondering, what is the difference between redis and the database, that I would like to use (back4app).
If I implement connect-redis
const RedisStore = require('connect-redis')(session);
const redis = require("redis").createClient();
let sess = {
store: new RedisStore({ host: 'localhost', 6379, client: redis }),
secret: cryptoString,
resave: true,
saveUninitialized: true,
cookie: {
maxAge: 1 * 60 * 1000,
},
}
server.use(session(sess));
the user object from back4app stills undefined. (Without redis the user object exists.)
As mentioned I have tryed Parse.User.logOut(). It doesn't work. The console says Parse.User is null.
Please, explain
what is the difference between back4app and redis? Do I need both?
how do I enable log out?
For all with the same problem. This is my other question in this context. It will help you to see the whole picture.
After a bit of research, I came to the conclusion that I can run multiple instances of Redis on my CentOS server for each NodeJS server I run (I use Redis to store sessions).
I have followed these instructions and both instances are running properly on two different ports.
On my NodeJS servers, I configured Redis as follows:
import * as session from "express-session";
var RedisStore = require('connect-redis')(session);
var redis = require("redis").createClient();
app.use(session(
{
secret: secret,
store: new RedisStore({ host: 'localhost', port: 6379, client: redis }),
cookie: { maxAge: 12 * 3600000 },
resave: true, saveUninitialized: true
}
));
One with port 6379 and the other with 6380.
I use req.session.regenerate to register a session.
Both login systems work perfectly individually. However, when I load anything on one application, the sessions of the other application are deleted (and need to be re-logged in).
What am I missing here?
The problem looks like it is the "session store" in Express and not your usage of Redis.
From the express session documentation:
NOTE be careful to generate unique IDs so your sessions do not conflict.
app.use(session({
genid: function(req) {
return genuuid() // use UUIDs for session IDs
},
secret: 'keyboard cat'
}))
Name: The name of the session ID cookie to set in the response (and read from in the request).
The default value is 'connect.sid'.
Specifically this warning explains your problem:
Note if you have multiple apps running on the same hostname (this is
just the name, i.e. localhost or 127.0.0.1; different schemes and
ports do not name a different hostname), then you need to separate the
session cookies from each other. The simplest method is to simply set
different names per app.
My App Engine logs show this.
Warning: connection.session() MemoryStore is not
designed for a production environment, as it will leak
memory, and will not scale past a single process.
Now my nodejs App can't use session. How I fix this?
Assuming you're referring to user authentication, note the following portion of the code from the AppEngine Node documentation and example:
// In production use the App Engine Memcache instance to store session data,
// otherwise fallback to the default MemoryStore in development.
if (config.get('NODE_ENV') === 'production' && config.get('MEMCACHE_URL')) {
sessionConfig.store = new MemcachedStore({
hosts: [config.get('MEMCACHE_URL')]
});
}
The default MemoryStore fallback is basically just there for development purposes; you should specify a more permanent/scaleable session storage of your choice for actual usage.
I think you are using PM2 or your server is running multiple threads, with default session storage mechanism. This won't scale pass a single thread solution, which is what you generally use in the development mode.
Hence, in-order to persist the session, you need to store it somewhere. For example, Redis.
const EXPRESS = require('express');
const APP = EXPRESS();
const EXPRESS_SESSION = require('express-session');
const REDIS_STORE = require('connect-redis')(EXPRESS_SESSION);
APP.use(EXPRESS_SESSION({
secret: 'YOUR_SECRET',
saveUninitialized: false,
resave: false,
store: new REDIS_STORE({ //storing the session in redis
host: 'localhost',
port: 6379, //redis port, should be 6379 by default
ttl: 300 //time-to-live, session will be destroyed if no activity in 5 mins
})
}));
Source of the code: Personal project
Using a session store fixed the error.
use this link.
https://github.com/expressjs/session#compatible-session-stores
In the README.md under
compatible session stores there is list of compatible session stores for expressjs.
i'm trying to set up a very basic session manager. So, first i came with this code:
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
// define session store
app.use(session({
secret: 'meh',
saveUninitialized: true,
resave: true,
store: new MongoStore({
db : 'express'
})
}));
My current setup is nodejs/express4 with all modules installed locally.
now, how can i share this app without having problems with connect-mongo? because, if i run it on other machine, i get this error:
"error connection to database:failed to connect to [127.0.0.1:27017]..."
Do you know what may be wrong ?
The most obvious mistake here would be: there is no running MongoDB instance on the other machine.
If you want to connect to a remote server you have to specify the location and (hopefully) credentials. Have a look at: https://github.com/kcbanner/connect-mongo#options
adapting an existing app to Express 4.4.x
Trying to implement redis session store, using the following code:
var express = require('express');
var session = require('express-session');
var RedisStore = require('connect-redis')(session);
var redis = require('redis').createClient();
app.use(session({
store: new RedisStore({
host: '1.1.1.1',
port: 1234,
prefix: 'yourprefix:',
client: redis
}),
secret: '.......',
resave: true,
saveUninitialized: true
}));
However, when I run this there is no key stored in redis and I cannot call req.session. I'm sure it's something really simple I've missed or included something in the code which is restricting it. Could also be redis settings?
Thanks
Ok, I just got lost in the documentation. Difficult to keep track of express-session, redis, connect-redis and entirely remember the difference between everything.
The simple solution was:
var redis = require('redis').createClient(port, host, auth);
Now it works. I believe I was able to use .createClient(); when the redis server was hosted locally, however now we've moved to a dedicated redis server - these attributes are required.
Thanks, hope this helps somebody else!