My express app uses PassportJS for storing auth session but it creates a ridiculous number of keys in the redis store and all the keys look the same:
"{\"cookie\":{\"originalMaxAge\":null,\"expires\":null,\"httpOnly\":true,\"path\":\"/\"},\"passport\":{}}"
They do have a TTL but the store is growing in size for no good reason. Any idea why these are being created? My keys looks like:
"sess:8R3A-k6dARJvxXFdAXr5nTG7MeC7JTxb"
"sess:s4VYC-k-nmfSf7n-qGQJimFmt30EYNDp"
"sess:BS7WO92Nyl5R0wAbJ-Vo9o8w1apu0kp7"
"sess:0B1AKS6-MCclPvOXV0nlvNio8U8fxyQO"
"sess:v0UWf60LMwKmMVZgo4RWumX313yPsiD0"
If I just eyeball it, roughly around 10 keys are being created every second or two.
This is how my session code looks:
var express = require('express'),
....
session = require('express-session'),
redisStore = require('connect-redis')(session);
...
app.use(express.static(path.resolve('./public')));
//Redis
var redisClient = redisHelper.init();
app.use(session({
secret: '...',
store: new redisStore({
client: redisClient,
ttl: 86400
}),
resave: false,
saveUninitialized: false,
cookie:{maxAge:86400}
}));
//Passport
app.use(passport.initialize());
app.use(passport.session());
...
Redis init function returns an instance of a redis client:
exports.init = function () {
redisClient = redis.createClient(config.redis.port, config.redis.server, {});
redisClient.auth(config.redis.auth);
redisClient.on('error', function (error) {
//TODO: log the error
winston.error('Error while talking to Redis', {message: error});
});
return redisClient;
};
Related
I am trying to add Redis to my website for session management but I get the below error:
UnhandledPromiseRejectionWarning: Error: The client is closed
Below is my code:
I have kept only the relevant redis lines to avoid verbosity.
const express = require('express');
const app = express();
const session = require('express-session');
const redis = require('redis');
const connectRedis = require('connect-redis');
const RedisStore = connectRedis(session)
const redisClient = redis.createClient({
host: 'localhost',
port: 6379
})
redisClient.on('error', function (err) {
console.log('Could not establish a connection with redis. ' + err);
});
redisClient.on('connect', function (err) {
console.log('Connected to redis successfully');
});
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: 'secret$%^134',
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // if true only transmit cookie over https
httpOnly: false, // if true prevent client side JS from reading the cookie
maxAge: 1000 * 60 * 10 // session max age in miliseconds
}
}))
app.use(
session({
name: 'AuthCookie',
secret: 'some secret string!',
resave: false,
saveUninitialized: true
})
);
app.listen(port, () => {
console.log("We've now got a server!");
console.log('Your routes will be running on http://localhost:3000');
});
I read that I should add await client.connect() but I am not sure
I am looking for guidance on setting up session based authentication with with Express-Session, connect-mongo, and Mongoose. Currently it's just generating a new UUID with every request and not saving anything to the sessions collection. Am I missing something obvious?
index.js
const mongoose = require("./db/connection");
const express = require("express");
const cors = require('cors')
const session = require('express-session')
const MongoStore = require("connect-mongo");
const app = express();
const { v4: uuidv4 } = require('uuid');
//Register .env file
require('dotenv').config()
//Middleware
app.use(express.json());
app.use(session({
genid: (req) => {
return uuidv4()
},
secret: process.env.EXPRESS_SESSION_SECRET,
resave: true,
saveUninitialized: false,
cookie: { maxAge: 24 * 60 * 60 * 1000 },
store: MongoStore.create({
client: mongoose.connection.getClient(),
dbName: process.env.MONGO_DB_NAME,
collectionName: "sessions",
stringify: false,
autoRemove: "interval",
autoRemoveInterval: 1
})
})
);
connection.js
const mongoose = require("mongoose");
require('dotenv').config()
mongoose.connect(`mongodb://devroot:devroot#localhost:27017/${process.env.MONGO_DB_NAME}?authSource=admin`, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
});
mongoose.connection
.on("open", () => console.log("The goose is open"))
.on("close", () => console.log("The goose is closed"))
.on("error", (error) => {
console.log(error);
process.exit();
})
module.exports = mongoose;
The setting saveUninitialized: false means that a session is established only if it contains some information, that is, if a statement like req.session.attribute = "value" is executed during request processing. If that does not happen, the session is not stored, and also no session cookie issued, so that the next request triggers a new session (with a new UUID), but which may again not be stored.
The author probably "solved" the issue by setting saveUninitialized: true, but this has the following consequences:
Every visitor to the website creates a new session entry (without any information in it) in the database even if they never interact with the site nor log on.
Every visitor gets a session cookie in their browser even before actually logging on.
I consider both these consequences undesirable and would therefore prefer saveUninitialized: false so that sessions without information are effectively not created.
Posting for visibility; this was related to:
saveUninitialized: false
Changing this to true forces save to the store.
I have a PassportJS authentication set up on my app with strategies for Facebook, Twitter, and Google, along with local. Here's what my authentication route currently looks like:
// /routes/auth-routes.js
import connectRedis from 'connect-redis';
import express from 'express';
import session from 'express-session';
import uuidv4 from 'uuid/v4';
import facebook from './auth-providers/facebook';
import google from './auth-providers/google';
import local from './auth-providers/local';
import twitter from './auth-providers/twitter';
const RedisStore = connectRedis(session);
const router = express.Router();
router.use(session({
name: process.env.SESSION_COOKIE,
genid: () => uuidv4(),
cookie: {
httpOnly: true,
sameSite: 'strict',
},
secret: process.env.SESSION_SECRET,
store: new RedisStore({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
ttl: 1 * 24 * 60 * 60, // In seconds
}),
saveUninitialized: false,
resave: false,
}));
// Social auth routes
router.use('/google', google);
router.use('/twitter', twitter);
router.use('/facebook', facebook);
router.use('/local', local);
// Logout
router.get('/logout', (req, res) => {
req.logout();
const cookieKeys = Object.keys(req.cookies);
if(cookieKeys.includes(process.env.USER_REMEMBER_COOKIE)) {
console.log('REMEMBER COOKIE EXISTS!');
const rememberCookie = process.env.USER_REMEMBER_COOKIE;
const sessionCookie = process.env.SESSION_COOKIE;
cookieKeys.forEach((cookie) => {
if(cookie !== rememberCookie && cookie !== sessionCookie) res.clearCookie(cookie);
});
res.redirect(req.query.callback);
} else {
console.log('NO REMEMBER');
req.session.destroy(() => {
cookieKeys.forEach((cookie) => {
res.clearCookie(cookie);
});
res.redirect(req.query.callback);
});
}
});
module.exports = router;
As apparent, I'm using Redis to store session cookies, which are then sent to the server along with all others cookies upon each page-reload. Here's my question:
Is this enough? Shouldn't I be validating the integrity of received session cookie by looking it up against the Redis store? But if I do that on every page load, won't that affect performance adversely? What's the standard way to handle this?
the repo is up at https://github.com/amitschandillia/proost/blob/master/web.
It look like you're missing part of the integration with PassportJS with express-session. Like the comment mentioned, express-session will store your session data in the redis store, and its key will be stored in the cookie sent to the user.
But you're logging in your users through passportJS, you need to connect these two middlewares together. From the passportJS docs :
http://www.passportjs.org/docs/configure/
Middleware
In a Connect or Express-based application, passport.initialize() middleware is required to initialize Passport. If your application uses persistent login sessions, passport.session() middleware must also be used.
app.configure(function() {
app.use(express.static('public'));
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
});
I have been struggling to count the number of users or session ids, when using connect-redis for sessions. I've referred to this: http://expressjs.com/guide.html#users-online
var express = require('express');
//var redis = require('redis');
//var db = redis.createClient();
var app = express();
var RedisStore = require('connect-redis')(express);
app.use(express.cookieParser());
app.use(express.session({
store: new RedisStore({
host: 'localhost',
port: 6379,
db: 2,
pass: 'RedisPASS'
}),
secret: '1234567890QWERTY'
}));
app.use(function(req, res, next){
var ua = req.headers['user-agent'];
db.zadd('online', Date.now(), ua, next);
});
app.use(function(req, res, next){
var min = 60 * 1000;
var ago = Date.now() - min;
db.zrevrangebyscore('online', '+inf', ago, function(err, users){
if (err) return next(err);
req.online = users;
next();
});
});
app.get('/', function(req, res) {
db.zadd('online', Date.now(), req.sessionID, next);
});
app.get('/', function(req, res){
res.send(req.online.length + ' users online');
});
app.listen(3000);
Obviously, the above code does not understand the variable db as there is no explicit redis.createClient(); ... Does connect-redis do the redis.createClient internally ?
Please help on how to count the number of session ids with connect-redis.
Yes, connect-redis does use redis.createClient() internally if you only provide it with credentials like you have above. You can also pass it a client directly.
var redisClient = redis.createClient(6379, "127.0.0.1", { auth_pass: "RedisPASS" });
redisClient.select(2);
app.use(express.session({
store: new RedisStore({
client: redisClient
}),
secret: '1234567890QWERTY'
}));
To get the number of sessions you'll want to query redis for the set of keys matching that key prefix. For connect-redis this is "sess:".
redisClient.keys("sess:*", function(error, keys){
console.log("Number of active sessions: ", keys.length);
});
As they state in the redis documentation, the keys command takes linear time so in large systems it's not a good idea to query this frequently. You'd be better off keeping a separate counter that you increment when a user signs in and decrement when a user signs out.
I've read this topic Node.js + express.js + passport.js : stay authenticated between server restart and I need exactly the same thing, but for Redis. I used such code:
var RedisStore = require('connect-redis')(express);
app.use(express.session({
secret: "my secret",
store: new RedisStore,
cookie: { secure: true, maxAge:86400000 }
}));
And it doesn't work. To connect Redis I use connect-redis module. What I'm doing wrong? Thanks!
UPD: I don't get any errors. To ensure auth processes succesfully, I added log-line, and it executes.
function(email, password, done) {
// asynchronous verification, for effect...
process.nextTick(function() {
findByEmail(email, function(err, user) {
if (!user) {
return done(null, false, {
message: 'Unknown user ' + email
});
}
if (user.password != password) {
return done(null, false, {
message: 'Invalid password'
});
}
//just logging that eveything seems fine
console.log("STATUS: User " + email + " authentificated succesfully");
return done(null, user);
})
});
}));
Log with express.logger() enabled was:
127.0.0.1 - - [Fri, 19 Oct 2012 05:49:09 GMT] "GET /ico/favicon.ico HTTP/1.1" 404 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4"
STATUS: User admin authentificated succesfully
I do suppose that there is nothing wrong with auth/users/credentials/serializing/deserializing itself. The problem is just passport cannot set cookie to Redis and the read it.
I should use
cookie: { secure: false, maxAge:86400000 }
try this out, instead of passing express to const RedisStore pass session.
const redis = require('redis');
const session = require('express-session');
const redisStore = require('connect-redis')(session);
const cookieParser = require('cookie-parser');
const app = require('../app');
app.app.use(cookieParser("secret"));
const rediscli = redis.createClient();
app.app.use(session({
secret: 'secret',
store: new redisStore({
host: '127.0.0.1',
port: 6379,
client: rediscli,
ttl: 260
}),
saveUninitialized: false,
resave: false
}));
What happens when you set the store explicitly? i.e. something along these lines in your app:
var redis = require('redis');
// This is host and port-dependent, obviously
var redisClient= redis.createClient(6379, 'localhost');
app.use(express.session({
secret: 'your secret',
/* set up your cookie how you want */
cookie: { maxAge: ... },
store: new (require('express-sessions'))({
storage: 'redis',
instance: redisClient
})
}));