count number of sessions in connect-redis - node.js

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.

Related

Angular with express, session is not persistent. Each request creates a new session

Original Question
I am using passport.js to do authentication in express, when I use req.flash('message', 'message content') in passport strategy, the flashed information is not under the normal session but 'sessions' and when I tried to retrieve the flashed message using req.flash(), it's an empty array.
I printed out the req
, it looks like this:
MemoryStore {
_events:
{ disconnect: [Function: ondisconnect],
connect: [Function: onconnect] },
_eventsCount: 2,
_maxListeners: undefined,
sessions:
{ gzNcx9b8rcWfDtJm03VnNJfhsNW8EJ7B:
'{"cookie":{"originalMaxAge":null,"expires":null,"httpOnly":true,"path":"/"},"flash":{"message":["emails has been taken, choose another one!"]}}' },
generate: [Function] },
sessionID: 'ffSa89VCV0Mj6uKLrEPMAdNMGLR2I5ML',
session:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true } },
_passport:
Somehow it opens a new session after redirecting to /api/signupFail. Could anyone help me with this?
Here is my middleware setup:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var logger = require('morgan');
var passport = require('passport');
require('./config/passport')(passport);
var cors = require('cors');
var session = require('express-session');
var flash = require('connect-flash');
var app = express();
var corsOptions = {
origin: 'http://localhost:4200',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
};
app.use(cors(corsOptions));
app.use(logger('dev'));
app.use(cookieParser('Thespywhodumpedme'));
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json());
var goalsRoute = require('./routes/goalsRoute');
var userRoute = require('./routes/userRoute');
// required for passport
app.use(flash());
app.use(session({ secret: 'keyboard cat',resave: true, saveUninitialized:true})); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
// use connect-flash for flash messages stored in session
app.use(express.static(path.join(__dirname, 'public')));
app.post('/api/signup', passport.authenticate('local-signup', {
successRedirect: '/api/user/suctest',
failureRedirect: '/api/signupFail',
failureFlash: true
}));
app.get('/api/signupFail', (req, res, next) => {
console.log(req.flash('message')); //this is an empty array
res.status(403).send('fail');
})
Here is my strategy setup:
module.exports = function(passport) {
passport.serializeUser((user, done) => {
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser((id, done) => {
db.User.getUserById(id, (err, result) => {
done(err, result[0]);
});
});
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
if(!email || !password ) { return done(null, false, req.flash('message','All fields are required.')); }
var salt = '7fa73b47df808d36c5fe328546ddef8b9011b2c6';
db.User.getUserByEmail(email, function(err, rows){
if (err) {
return done(req.flash('message',err));
}
if(rows.length > 0){
return done(null, false, req.flash('message',"emails has been taken, choose another!"));
}
salt = salt+''+password;
var encPassword = crypto.createHash('sha1').update(salt).digest('hex');
var newUser = {
name: req.body.name,
email: email,
password: encPassword,
sign_up_time: new Date()
}
db.User.addOneUser(newUser, (err, result) => {
db.User.getUserByEmail(email, (err, result) => {
return done(err, result[0]);
})
});
});
}));
};
Update
At first, I thought it has something to do with flash, but then after printing session out, I found that a new session is created after redirecting. I thought it has something to do with the backend setup. Accidentally, I found this problem doesn't exist when I sent the request from postman. That's when I figured out it might have something to do with Angular which is listening on port 4200 while express listening on port 3000. I was sending the request to port 3000 by hardcoding the port number in httpClient. After I set up a proxy that redirects all API call to port 3000. Everything works just fine.
OK, it turns out that it has nothing to do with the backend. Everything works just fine when I sent the request through postman. The problem is with the frontend, I am using Angular 6, Angular is listening on port 4200 while express listening on port 3000. I set up a proxy in Angular that redirects all API call to localhost: 3000 and the session is persistent.

Passport.SocketIo - How to get a list of online users with NodeJS, Express and Passport

i am finish sessionStore with MongoStore every login is being performed correctly and sessions are being written to the database without errors. I am using this package github.com/jfromaniello/passport.socketio to align the passport with socket io but I have already looked for several places about how after login make the treatment of the sessionStorageso it lists which users with names are online and offline, Could show me a light on this?
app.js
var express = require('express');
var mongoose = require('mongoose');
var path = require('path');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var session = require('express-session');
const MongoStore = require('connect-mongo')(session);
var flash = require('connect-flash');
var logger = require('morgan');
var passport = require('passport');
var passportSetup = require('./passport-setup');
// import routes
var routes = require('./routes');
// setup express app
var app = express();
app.use(logger());
// setup connection with mongodb
mongoose.connect( process.env.MONGODB_URI || "mongodb://smachs:***#d***.mlab.com:****/****-messenger",
(err, db)=> {
if (err) return new Error(err);
console.log('🔐 Conexão estabelecida com banco de dados!');
});
// setup passport from different class
passportSetup();
// set view engine and connection of application
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(bodyParser.urlencoded({extended:false}));
app.use(cookieParser());
// session storage based in mongodb
var sessionStore = new MongoStore({
url: 'mongodb://smachs:***#d***.mlab.com:****/****-messenger',
ttl: 1 * 24 * 60 * 60, // = 1 days. Default
autoReconnect: true
})
// setup session based in express-session
app.use(session({
secret:"58585858585858",
key: "connect.sid",
resave: false,
saveUninitialized: false,
store: sessionStore
}));
app.use(flash());
// public directory
app.use(express.static(__dirname + '/public'));
// passport staff
app.use(passport.initialize());
app.use(passport.session());
// start routes
app.use(routes);
// start server
var port = process.env.PORT || 3000;
var server = app.listen(port, () => { console.log('🌐 Servidor iniciado em localhost:', port); });;
// setup socket.io and passport.socketio packages
var io = require('socket.io').listen(server);
var passportSocketIo = require("passport.socketio");
// setup session found in express-session
io.use(passportSocketIo.authorize({
cookieParser: cookieParser, // the same middleware you registrer in express
key: 'connect.sid', // the name of the cookie where express/connect stores its session_id
secret: '58585858585858', // the session_secret to parse the cookie
store: sessionStore, // we NEED to use a sessionstore. no memorystore please
success: onAuthorizeSuccess, // *optional* callback on success - read more below
fail: onAuthorizeFail, // *optional* callback on fail/error - read more below
}));
// setup route just for clients authenticate
function ensureAutheticated(req, res, next) {
if (req.isAuthenticated()) next();
else {
req.flash("info", "Você precisa estar logado para visualizar essa página!");
res.redirect('/login');
}
}
// setup current online clients
var User = require('./models/user');
app.use((req, res, next) => {
res.locals.currentUser = req.user;
res.locals.errors = req.flash('error');
res.locals.infos = req.flash('info');
next();
});
// callback from passport.socketio
function onAuthorizeSuccess(data, accept) {
console.log('🗲 Passport-Socket.IO conectado com sucesso');
io.on('connection', function (socket) {
console.log("🗲 Socket.IO-Native conectado com sucesso");
});
// get current user online after authentication
io.on('connection', function (socket) {
// get user details of documents in database
app.get('/user-online', ensureAutheticated, (req, res) => {
User.find()
.sort({ createdAd: 'descending' })
.exec((err, users) => {
if (err) return next(err);
// render response
res.send({
users: users
})
});
});
});
accept();
}
function onAuthorizeFail(data, message, error, accept) {
console.log('failed connection to socket.io:', data, message);
if (error)
accept(new Error(message));
}
user.js
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
const SALT_FACTOR = 10;
var userSchema = mongoose.Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
displayName: String,
bio: String
});
userSchema.methods.name = function() { return this.displayName || this.username;}
function noop() { };
userSchema.pre('save', function(done) {
var user = this;
console.log('USER: ' + JSON.stringify( user));
if (!( user.isModified('password'))) return done();
bcrypt.genSalt(SALT_FACTOR, function(err, salt) {
if (err) return done(err);
bcrypt.hash(user.password, salt, noop,
function (err, hashedPassword) {
if (err) return done(err);
user.password = hashedPassword;
done();
});
});
});
userSchema.methods.checkPassword = function(guess, done){
bcrypt.compare(guess, this.password, function(err, isMatch){
done(err,isMatch);
});
};
var User = mongoose.model('User', userSchema);
module.exports = User;
I was trying after login to make an query in a collection to list the users I logged but it is limited to only 1 user and gives me no option to treat this result better, thank you very much for the help they give me!
You can track connection, disconnect, login and logout events to create a list of online users.
You can manage online users in RAM or you can use redis for that. Following code snippet may help you achieve your goal -
// Store userIds here
let onlineUsers = [];
io.on('connection', function (socket) {
socket.on('login', (userTokenOrId) => {
// store this to onlineUsers or redis
// Other stuff
});
socket.on('logout', (userTokenOrId) => {
// remove this from onlineUsers or redis
// Other stuff
});
socket.on('disconnect', (userTokenOrId) => {
// remove this from onlineUsers or redis
// Other stuff
});
});
For better use, You can manage one array of objects to store userId and list of socketIds for same and one object to map socketId to userId. This way you can track is one user is online on different browsers/system.

How can I get session data from RedisStore (connect-redis) using SessionID?

So the following snippet shows how RedisStore is set up and picks up the sessionID from incoming GET requests. However, sessionStore.get( req.cookies['connect.sid'] ) evaluates to undefined.
Could someone please point out what I am doing wrong? An example sessionID that is returned from req.cookies['connect.sid'] looks like this:
s:_8pvC4Zju4GjMOvN6Y4ej8SRVYvopn8C.ctuA/+pQnZt96bLgOW1xizHOLI2nBuH4vzO9Agm26Lk
var expressSession = require('express-session');
var RedisStore = require('connect-redis')(expressSession);
var sessionStore = new RedisStore({host: '127.0.0.1', port: 6379});
app.use(expressSession({store: sessionStore,
secret: 'xxxx' }));
app.get("/*", function(req, res, next) {
if(typeof req.cookies['connect.sid'] !== 'undefined') {
console.log('sessionID: ', req.cookies['connect.sid']);
console.log( sessionStore.get(req.cookies['connect.sid'] ));
}
next();
});
sessionStore.get is a async method, you need pass a callback to this method, like this:
sessionStore.get(req.cookies['connect.sid'], function (err, session) {
console.log(session);
})
Watch this docs about express-session.

How to share sessions with Socket.IO 1.x and Express 4.x?

How can I share a session with Socket.io 1.0 and Express 4.x? I use a Redis Store, but I believe it should not matter. I know I have to use a middleware to look at cookies and fetch session, but don't know how. I searched but could not find any working
var RedisStore = connectRedis(expressSession);
var session = expressSession({
store: new RedisStore({
client: redisClient
}),
secret: mysecret,
saveUninitialized: true,
resave: true
});
app.use(session);
io.use(function(socket, next) {
var handshake = socket.handshake;
if (handshake.headers.cookie) {
var str = handshake.headers.cookie;
next();
} else {
next(new Error('Missing Cookies'));
}
});
The solution is surprisingly simple. It's just not very well documented. It is possible to use the express session middleware as a Socket.IO middleware too with a small adapter like this:
sio.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res, next);
});
Here's a full example with express 4.x, Socket.IO 1.x and Redis:
var express = require("express");
var Server = require("http").Server;
var session = require("express-session");
var RedisStore = require("connect-redis")(session);
var app = express();
var server = Server(app);
var sio = require("socket.io")(server);
var sessionMiddleware = session({
store: new RedisStore({}), // XXX redis server config
secret: "keyboard cat",
});
sio.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res || {}, next);
});
app.use(sessionMiddleware);
app.get("/", function(req, res){
req.session // Session object in a normal request
});
sio.sockets.on("connection", function(socket) {
socket.request.session // Now it's available from Socket.IO sockets too! Win!
});
server.listen(8080);
Just a month and a half ago I dealt with the same problem and afterwards wrote an extensive blog post on this topic which goes together with a fully working demo app hosted on GitHub. The solution relies upon express-session, cookie-parser and connect-redis node modules to tie everything up. It allows you to access and modify sessions from both the REST and Sockets context which is quite useful.
The two crucial parts are middleware setup:
app.use(cookieParser(config.sessionSecret));
app.use(session({
store: redisStore,
key: config.sessionCookieKey,
secret: config.sessionSecret,
resave: true,
saveUninitialized: true
}));
...and SocketIO server setup:
ioServer.use(function (socket, next) {
var parseCookie = cookieParser(config.sessionSecret);
var handshake = socket.request;
parseCookie(handshake, null, function (err, data) {
sessionService.get(handshake, function (err, session) {
if (err)
next(new Error(err.message));
if (!session)
next(new Error("Not authorized"));
handshake.session = session;
next();
});
});
});
They go together with a simple sessionService module I made which allows you to do some basic operations with sessions and that code looks like this:
var config = require('../config');
var redisClient = null;
var redisStore = null;
var self = module.exports = {
initializeRedis: function (client, store) {
redisClient = client;
redisStore = store;
},
getSessionId: function (handshake) {
return handshake.signedCookies[config.sessionCookieKey];
},
get: function (handshake, callback) {
var sessionId = self.getSessionId(handshake);
self.getSessionBySessionID(sessionId, function (err, session) {
if (err) callback(err);
if (callback != undefined)
callback(null, session);
});
},
getSessionBySessionID: function (sessionId, callback) {
redisStore.load(sessionId, function (err, session) {
if (err) callback(err);
if (callback != undefined)
callback(null, session);
});
},
getUserName: function (handshake, callback) {
self.get(handshake, function (err, session) {
if (err) callback(err);
if (session)
callback(null, session.userName);
else
callback(null);
});
},
updateSession: function (session, callback) {
try {
session.reload(function () {
session.touch().save();
callback(null, session);
});
}
catch (err) {
callback(err);
}
},
setSessionProperty: function (session, propertyName, propertyValue, callback) {
session[propertyName] = propertyValue;
self.updateSession(session, callback);
}
};
Since there is more code to the whole thing than this (like initializing modules, working with sockets and REST calls on both the client and the server side), I won't be pasting all the code here, you can view it on the GitHub and you can do whatever you want with it.
express-socket.io-session
is a ready-made solution for your problem. Normally the session created at socket.io end has different sid than the ones created in express.js
Before knowing that fact, when I was working through it to find the solution, I found something a bit weird. The sessions created from express.js instance were accessible at the socket.io end, but the same was not possible for the opposite. And soon I came to know that I have to work my way through managing sid to resolve that problem. But, there was already a package written to tackle such issue. It's well documented and gets the job done. Hope it helps
Using Bradley Lederholz's answer, this is how I made it work for myself. Please refer to Bradley Lederholz's answer, for more explanation.
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io');
var cookieParse = require('cookie-parser')();
var passport = require('passport');
var passportInit = passport.initialize();
var passportSession = passport.session();
var session = require('express-session');
var mongoStore = require('connect-mongo')(session);
var mongoose = require('mongoose');
var sessionMiddleware = session({
secret: 'some secret',
key: 'express.sid',
resave: true,
httpOnly: true,
secure: true,
ephemeral: true,
saveUninitialized: true,
cookie: {},
store:new mongoStore({
mongooseConnection: mongoose.connection,
db: 'mydb'
});
});
app.use(sessionMiddleware);
io = io(server);
io.use(function(socket, next){
socket.client.request.originalUrl = socket.client.request.url;
cookieParse(socket.client.request, socket.client.request.res, next);
});
io.use(function(socket, next){
socket.client.request.originalUrl = socket.client.request.url;
sessionMiddleware(socket.client.request, socket.client.request.res, next);
});
io.use(function(socket, next){
passportInit(socket.client.request, socket.client.request.res, next);
});
io.use(function(socket, next){
passportSession(socket.client.request, socket.client.request.res, next);
});
io.on('connection', function(socket){
...
});
...
server.listen(8000);
Working Example for PostgreSQL & Solving the problem of getting "an object with empty session info and only cookies":
Server-Side (Node.js + PostgreSQL):
const express = require("express");
const Server = require("http").Server;
const session = require("express-session");
const pg = require('pg');
const expressSession = require('express-session');
const pgSession = require('connect-pg-simple')(expressSession);
const PORT = process.env.PORT || 5000;
const pgPool = new pg.Pool({
user : 'user',
password : 'pass',
database : 'DB',
host : '127.0.0.1',
connectionTimeoutMillis : 5000,
idleTimeoutMillis : 30000
});
const app = express();
var ioServer = require('http').createServer(app);
var io = require('socket.io')(ioServer);
var sessionMiddleware = session({
store: new RedisStore({}), // XXX redis server config
secret: "keyboard cat",
});
io.use(function(socket, next) {
session(socket.request, {}, next);
});
app.use(session);
io.on("connection", socket => {
const ioSession = socket.request.session;
socket.on('userJoined', (data) => {
console.log('---ioSession---', ioSession)
}
}
Client-Side (react-native app):
To solve the problem of getting "empty session object" you need to add withCredentials: true
this.socket = io(`http://${ip}:5000`, {
withCredentials: true,
});
I have kinda solved it, but it is not perfect. Does not support signed cookies etc. I used express-session 's getcookie function. The modified function is as follows:
io.use(function(socket, next) {
var cookie = require("cookie");
var signature = require('cookie-signature');
var debug = function() {};
var deprecate = function() {};
function getcookie(req, name, secret) {
var header = req.headers.cookie;
var raw;
var val;
// read from cookie header
if (header) {
var cookies = cookie.parse(header);
raw = cookies[name];
if (raw) {
if (raw.substr(0, 2) === 's:') {
val = signature.unsign(raw.slice(2), secret);
if (val === false) {
debug('cookie signature invalid');
val = undefined;
}
} else {
debug('cookie unsigned')
}
}
}
// back-compat read from cookieParser() signedCookies data
if (!val && req.signedCookies) {
val = req.signedCookies[name];
if (val) {
deprecate('cookie should be available in req.headers.cookie');
}
}
// back-compat read from cookieParser() cookies data
if (!val && req.cookies) {
raw = req.cookies[name];
if (raw) {
if (raw.substr(0, 2) === 's:') {
val = signature.unsign(raw.slice(2), secret);
if (val) {
deprecate('cookie should be available in req.headers.cookie');
}
if (val === false) {
debug('cookie signature invalid');
val = undefined;
}
} else {
debug('cookie unsigned')
}
}
}
return val;
}
var handshake = socket.handshake;
if (handshake.headers.cookie) {
var req = {};
req.headers = {};
req.headers.cookie = handshake.headers.cookie;
var sessionId = getcookie(req, "connect.sid", mysecret);
console.log(sessionId);
myStore.get(sessionId, function(err, sess) {
console.log(err);
console.log(sess);
if (!sess) {
next(new Error("No session"));
} else {
console.log(sess);
socket.session = sess;
next();
}
});
} else {
next(new Error("Not even a cookie found"));
}
});
// Session backend config
var RedisStore = connectRedis(expressSession);
var myStore = new RedisStore({
client: redisClient
});
var session = expressSession({
store: myStore,
secret: mysecret,
saveUninitialized: true,
resave: true
});
app.use(session);
Now, the original accepted answer doesn't work for me either. Same as #Rahil051, I used express-socket.io-session module, and it still works. This module uses cookie-parser, to parse session id before entering express-session middleware.
I think it's silmiar to #pootzko, #Mustafa and #Kosar's answer.
I'm using these modules:
"dependencies":
{
"debug": "^2.6.1",
"express": "^4.14.1",
"express-session": "^1.15.1",
"express-socket.io-session": "^1.3.2
"socket.io": "^1.7.3"
}
check out the data in socket.handshake:
const debug = require('debug')('ws');
const sharedsession = require('express-socket.io-session');
module.exports = (server, session) => {
const io = require('socket.io').listen(server);
let connections = [];
io.use(sharedsession(session, {
autoSave: true,
}));
io.use(function (socket, next) {
debug('check handshake %s', JSON.stringify(socket.handshake, null, 2));
debug('check headers %s', JSON.stringify(socket.request.headers));
debug('check socket.id %s', JSON.stringify(socket.id));
next();
});
io.sockets.on('connection', (socket) => {
connections.push(socket);
});
};

ExpressJS & Websocket & session sharing

I'm trying to make a chat application based on Node.js. I'd like to force websocket server (ws library) to using ExpressJS session system. Unfortunately, I've got stuck. MemoryStore hashes used to get sessions' data are different than session IDs in cookies. Could somebody explain me what I'm doing wrong?
Websocket server code part:
module.exports = function(server, clients, express, store) {
server.on('connection', function(websocket) {
var username;
function broadcast(msg, from) {...}
function handleMessage(msg) {...}
express.cookieParser()(websocket.upgradeReq, null, function(err) {
var sessionID = websocket.upgradeReq.cookies['sid'];
//I see same value in Firebug
console.log(sessionID);
//Shows all hashes in store
//They're shorter than sessionID! Why?
for(var i in store.sessions)
console.log(i);
store.get(sessionID, function(err, session) {
websocket.on('message', handleMessage);
//other code - won't be executed until sessionID in store
websocket.on('close', function() {...});
});
});
});
}
store object definition:
var store = new express.session.MemoryStore({
reapInterval: 60000 * 10
});
app configuration:
app.configure(function() {
app.use(express.static(app.get("staticPath")));
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({
store: store,
secret: "dO_ob",
key: "sid"
}));
});
Part of main code:
var app = express();
var httpServer = http.createServer(app);
var websocketServer = new websocket.Server({server: httpServer});
httpServer.listen(80);
Sample debugging output:
- websocket.upgradeReq.headers.cookie "sid=s%3A64a%2F6DZ4Mab8H5Q9MTKujmcw.U8PJJIR%2BOgONY57mZ1KtSPx6XSfcn%2FQPZ%2FfkGwELkmM"
- websocket.upgradeReq.cookies["sid"] "s:64a/6DZ4Mab8H5Q9MTKujmcw.U8PJJIR+OgONY57mZ1KtSPx6XSfcn/QPZ/fkGwELkmM"
- i "64a/6DZ4Mab8H5Q9MTKujmcw"
I found this works for me. Not sure it's the best way to do this though. First, initialize your express application:
// whatever your express app is using here...
var session = require("express-session");
var sessionParser = session({
store: session_store,
cookie: {secure: true, maxAge: null, httpOnly: true}
});
app.use(sessionParser);
Now, explicitly call the session middleware from the WS connection. If you're using the express-session module, the middleware will parse the cookies by itself. Otherwise, you might need to send it through your cookie-parsing middleware first.
If you're using the websocket module:
ws.on("request", function(req){
sessionParser(req.httpRequest, {}, function(){
console.log(req.httpRequest.session);
// do stuff with the session here
});
});
If you're using the ws module:
ws.on("connection", function(req){
sessionParser(req.upgradeReq, {}, function(){
console.log(req.upgradeReq.session);
// do stuff with the session here
});
});
For your convenience, here is a fully working example, using express, express-session, and ws:
var app = require('express')();
var server = require("http").createServer(app);
var sessionParser = require('express-session')({
secret:"secret",
resave: true,
saveUninitialized: true
});
app.use(sessionParser);
app.get("*", function(req, res, next) {
req.session.working = "yes!";
res.send("<script>var ws = new WebSocket('ws://localhost:3000');</script>");
});
var ws = new require("ws").Server({server: server});
ws.on("connection", function connection(req) {
sessionParser(req.upgradeReq, {}, function(){
console.log("New websocket connection:");
var sess = req.upgradeReq.session;
console.log("working = " + sess.working);
});
});
server.listen(3000);
I was able to get this working. I think you need to specify the secret on cookieParser instead of session store.
Example from my app:
var app = express();
var RedisStore = require('connect-redis')(express);
var sessionStore = new RedisStore();
var cookieParser = express.cookieParser('some secret');
app.use(cookieParser);
app.use(express.session({store: sessionStore}));
wss.on('connection', function(rawSocket) {
cookieParser(rawSocket.upgradeReq, null, function(err) {
var sessionID = rawSocket.upgradeReq.signedCookies['connect.sid'];
sessionStore.get(sessionID, function(err, sess) {
console.log(sess);
});
});
});
Feb 2022 update:
verifyClient is now discouraged. New methods of doing this is described in an issue comment.
Consult the example code for session parsing and verification for a full usage example. Sample of the verification function:
server.on('upgrade', function (request, socket, head) {
console.log('Parsing session from request...');
sessionParser(request, {}, () => {
if (!request.session.userId) {
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
socket.destroy();
return;
}
console.log('Session is parsed!');
wss.handleUpgrade(request, socket, head, function (ws) {
wss.emit('connection', ws, request);
});
});
});
Original answer:
In version 3.2.0 of ws you have to do it a bit differently.
There is a full working example of express session parsing in the ws repo, specifically using a new feature verifyClient.
A very brief usage summary:
const sessionParser = session({
saveUninitialized: false,
secret: '$eCuRiTy',
resave: false
})
const server = http.createServer(app)
const wss = new WebSocket.Server({
verifyClient: (info, done) => {
console.log('Parsing session from request...')
sessionParser(info.req, {}, () => {
console.log('Session is parsed!')
done(info.req.session.userId)
})
},
server
})
wss.on('connection', (ws, req) => {
ws.on('message', (message) => {
console.log(`WS message ${message} from user ${req.session.userId}`)
})
})
WS v3.0.0 and above, has changed the behaviour so the given answers won't work out of the box for those versions. For current versions, the signature of the connection method is [function(socket, request)] and the socket no longer contains a reference to the request.
ws.on(
'connection',
function (socket, req)
{
sessionParser(
req,
{},
function()
{
console.log(req.session);
}
);
}
);
Currently, below is my workaround which is working fine. I just don't know it's disadvantages and security. I just prevent the server from listening if it doesn't have a session. (Share session from express-session to ws)
I haven't fully tested this though.
var http = require('http');
var express = require('express');
var expressSession = require('express-session');
var router = express.Router();
var app = express();
const server = http.createServer(app);
router.get('/', function(req, res, next) {
if(req.session.user_id) {
// Socket authenticated
server.listen(8080, function listening(){});
}
});

Resources