Passing express session to socket.io - node.js

https://github.com/xpepermint/socket.io-express-session
This is one of many ways i tried.
I tried all of these answers as well:
How to share sessions with Socket.IO 1.x and Express 4.x?
And some more...It all gives me the exaclty same result:
throw new TypeError("Parameter 'url' must be a string, not " + typeof
url)
^ TypeError: Parameter 'url' must be a string, not undefined
I already tried every solution i found on the internet, basically everything leads me to the same error.
At some point i simply tried copying and pasting examples found and they give me the exactly same results.
I guess something went wrong after one of the middleware updates. How to fix it? Is there another, reliable way to share express session with socket.io?
var Session = require('express-session');
var session = Session({ secret: 'pass', resave: true, saveUninitialized: true });
var cookieParser = require('cookie-parser');
var express = require('express');
var app = express();
app.use(cookieParser());
app.use(session); // session support
app.get('/', function (req, res) {
req.session.uid = 1;
res.send('Hi user #' + req.session.uid);
});
var http = require('http').createServer(app);
http.listen(3000, function(){
console.log('listening on *:3000');
});
var ios = require('socket.io-express-session');
var io = require('socket.io')(http);
io.use(ios(session)); // session support
io.on('connection', function(socket){
console.log(socket.handshake.session);
});
Above is an example returning exactly this error.

The error occurs in the stage when you use io.use(...)
The problem is that session middle ware expects to find the session in req.orginalUrl. A socket does not have an orginalUrl. To fix this, simply assign one...
io.use(function(socket, next){
socket.request.originalUrl = socket.request.url;
session(socket.request, socket.request.res, next);
});
The next problem is going to be that session(...) expects the cookies to be parsed. We can use 'cookie-parser' for this:
var cookieParse = cookieParser();
io.use(function(socket, next){
cookieParse(socket.request, socket.request.res, next);
});
After that we can simply bridge passport and socket.io:
var passInit = passport.initialize();
var passSess = passport.session();
io.use(function(socket, next){
passInit(socket.request, socket.request.res, next);
});
io.use(function(socket, next){
passSess(socket.request, socket.request.res, next);
});
Now in our io.on('connection') we can see socket.request.user
io.on('connection', function(socket){
console.log("socket connection");
console.log(socket.request.user)
console.log(socket.request.session.passport.user);
});

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

Related

Session variable is not available across routes

I am facing this problem from long time and finally I decided to post it here.
I am storing the session variable in one route and when I try to access it from other route it is not defined.
server.js file
var express=require('express');
app=express();
var port=process.env.PORT || 8080;
var mongoose=require('mongoose');
var router=express.Router();
var appRoutes=require('./routes/api')(router);
var session=require('express-session');
var MongoStore=require('connect-mongo')(session);
app.use(bodyParser.urlencoded({extended:true}));
app.use(session({
secret:'hardik',
saveUninitialized: true,
resave: true,
store: new MongoStore({mongooseConnection:mongoose.connection})
}));
app.listen(port,function(){
console.log('running on port '+port);
});
api.js
router.post('/authenticate',function(req,res){
var token=jwt.sign({
email:req.body.email
},secret);
var profile=req.body.profile;
req.session.token=token;
console.log(req.session);
console.log(req.session.token);
}
and after this it will show the token if I log just below that line but in any other routes if I try to log it, it gives undefined.
any help is invited.
If you are using passport after user login you can match all route with * and store the session with req.locals
app.get('*', function(req, res, next){
res.locals.user = req.user || null;
next();
});
Add this line before you start your server

I cannot send chat messages with socket.io

I could send messages before, but I shared passport's sessions with express and socket.io using redis by following this tutorial http://www.scotthasbrouck.com/blog/2016/3/18/passportjs-express-session-with-sockeio and now I can no longer send any chat messages.
Also, in my routes req.isAuthenticated() always returns false but I didn't have this problem before.
This is my server.js.
// server.js
// set up ======================================================================
// get all the tools we need
var express = require('express');
var app = express();
var server = require('http').Server(app);
var port = process.env.PORT || 8080;
var mongoose = require('mongoose');
var passport = require('passport');
var flash = require('connect-flash');
var morgan = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var configDB = require('./config/database.js');
var http = require('http').Server(app);
var io = require('socket.io')(http);
var passportSocketIo = require('passport.socketio');
var session = require('express-session');
var chatdb = require('./app/models/chat.js');
var user = require('./app/models/user.js');
var redis = require('redis');
var client = redis.createClient();
var RedisStore = require('connect-redis')(session);
// configuration ===============================================================
mongoose.connect(configDB.url); // connect to our database
require('./config/passport')(passport); // pass passport for configuration
// set up our express application
app.use(morgan('dev')); // log every request to the console
app.use(cookieParser()); // read cookies (needed for auth)
app.use(bodyParser()); // get information from html forms
app.set('view engine', 'ejs'); // set up ejs for templating
var sessionStore = new RedisStore({ host: 'localhost', port: 6379, client: client, ttl: 260 });
// required for passport
app.use(session({
secret: 'secret',
store: sessionStore,
resave: true,
saveUninitialized: true,
cookie: {
secure: process.env.ENVIRONMENT !== 'development' && process.env.ENVIRONMENT !== 'test',
maxAge: 2419200000
},
}));
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
app.use(flash()); // use connect-flash for flash messages stored in session
app.use(express.static(__dirname + '/views'));
app.use(express.static(__dirname + '/public'));
io.use(passportSocketIo.authorize({
key: 'connect.sid',
secret: 'secret',
store: sessionStore,
passport: passport,
cookieParser: cookieParser,
}));
var eventSocket = io.of('/chat');
// socket.io
io.on('connection', function (socket) {
console.log('a user connected');
socket.on('chat message', function(msg){
var name = "efe";
chatdb.saveMsg({name: name, msg: msg}, function(err){
if(err) throw err;
io.emit('chat message', msg);
});
if (socket.request.user && socket.request.user.logged_in) {
console.log(socket.request.user);
}
});
});
// routes ======================================================================
require('./app/routes.js')(app, passport); // load our routes and pass in our app and fully configured passport
// launch ======================================================================
http.listen(port);
console.log('The magic happens on port ' + port);
I had the same problem. I tried to implement passport.socketio like you but I solved my problem without it.
Here is my middleware :
// Express session middleware
var sessionMiddleware = session({ secret: 'secret-key', resave: true, saveUninitialized: true });
// Send Express sessionMiddleware through socket.io
io.use(function(socket, next){
// Params: request object, response object and callback.
sessionMiddleware(socket.request, {}, next);
});
The second parameter (response object) of sessionMiddleware function is empty. This will renders the session read-only.
And Here is how I use passport js session variable to get user informations :
io.on("connection", function(socket){
socket.on('chat message', function(msg){
var passport = socket.request.session.passport; // Get the passport variable
// Check if passport session exists
if (passport && typeof passport !== 'undefined'){
var userSession = passport.user; // Get the user session
// Build JSON with user session data
var message = {
message : msg,
name : userSession.name,
avatar : userSession.avatar
};
io.emit('chat message', message); // Emit my socket
}
});
});
Hope it help.
This was how i was able to solve it.
var express = require('express');
var app = express();
var server = require('http').Server(app);
var port = process.env.PORT || 8080;
var mongoose = require('mongoose');
var passport = require('passport');
var flash = require('connect-flash');
var morgan = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require('express-session');
// Add connect-mongo to project - npm install connect-mongo
var MongoStore = require('connect-mongo')(session);
var configDB = require('./config/database.js');
var http = require('http').Server(app);
var io = require('socket.io')(http);
var chatdb = require('./app/models/chat.js');
// configuration ===============================================================
// NOTE: This might need to be put into a callback/promise inside an initialize function
var db = mongoose.connect(configDB.url); // connect to our database
require('./config/passport')(passport); // pass passport for configuration
// set up our express application
app.use(morgan('dev')); // log every request to the console
app.use(cookieParser()); // read cookies (needed for auth)
app.use(bodyParser()); // get information from html forms
app.set('view engine', 'ejs'); // set up ejs for templating
var mongoStore = new MongoStore({
mongooseConnection: db.connection,
});
app.use(session({
secret: 'secret',
clear_interval: 900,
cookie: { maxAge: 2 * 60 * 60 * 1000 },
store: mongoStore,
}));
// required for passport
app.use(session({ secret: 'secret' })); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
app.use(flash()); // use connect-flash for flash messages stored in session
app.use(express.static(__dirname + '/views'));
app.use(express.static(__dirname + '/public'));
// Intercept Socket.io's handshake request
io.use(function(socket, next) {
// Use the 'cookie-parser' module to parse the request cookies
cookieParser('secret')(socket.request, {}, function(err) {
// Get the session id from the request cookies
var sessionId = socket.request.signedCookies ? socket.request.signedCookies['connect.sid'] : undefined;
if (!sessionId) return next(new Error('sessionId was not found in socket.request'), false);
// Use the mongoStorage instance to get the Express session information
mongoStore.get(sessionId, function(err, session) {
if (err) return next(err, false);
if (!session) return next(new Error('session was not found for ' + sessionId), false);
// Set the Socket.io session information
socket.request.session = session;
// Use Passport to populate the user details
passport.initialize()(socket.request, {}, function() {
passport.session()(socket.request, {}, function() {
// This will prohibit non-authenticated users from connecting to your
// SocketIO server.
if (socket.request.user) {
next(null, true);
} else {
next(new Error('User is not authenticated'), false);
}
});
});
});
});
});
// socket.io
io.on('connection', function(socket) {
socket.on('chat message', function(msg) {
console.log(socket.request.user.facebook.name || socket.request.user.local.email);
var name = socket.request.user.facebook.name || socket.request.user.local.email;
chatdb.saveMsg({
name: name,
msg: msg
}, function(err) {
if (err) throw err;
io.emit('chat message', msg);
});
});
socket.on('disconnect', function() {
console.log('user disconnected');
});
});
// routes ======================================================================
require('./app/routes.js')(app, passport); // load our routes and pass in our app and fully configured passport
// launch ======================================================================
http.listen(port);
console.log('The magic happens on port ' + port);

NodeJS - Access sessions inside socket

I spent 2 days on accessing sessions (log user in) trough sockets.
I already have users in my mongodb, and i can indeed login/register them without sockets (trough post or get). All i need now is the same for sockets.
I have tried these SO solutions:
socket.io and express 4 sessions
How to share sessions with Socket.IO 1.x and Express 4.x?
socket.io and session?
And spend a lot of time googling. And trying different things.
I typically get undefined errors, or deprecated error, or nothing happens at all without errors.
After following the tutorials above, and doing some tweaks myself, my code went from "hard to read" to "too hard to read, must start from scratch". So i wont put up my code here.
Instead, can someone share their bare-minimum code on what it takes to access the sessions inside sockets? With explanations would would be highly appreciated.
UPDATE
I followed this: How to share sessions with Socket.IO 1.x and Express 4.x?
And i got a few issues. It still does not work. the session is empty. Am i doing something wrong? Full code:
var express = require("express");
var Server = require("http").Server;
var session = require("express-session");
var RedisStore = require("connect-redis")(session);
var SESSION;
var app = express();
var server = Server(app);
var sio = require("socket.io")(server);
var sessionMiddleware = session({
store: new RedisStore(),
secret: "keyboard cat",
resave: true,
saveUninitialized: true
});
sio.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res, next);
});
app.use(sessionMiddleware);
app.get("/*", function(req, res){
SESSION = req.session
req.session.name="THE NAME"; // <<<< SHOULDN'T THIS WORK? ITS UNDEFINED
if(req.path=="/"){
res.sendFile(__dirname+"/index.html");
}else{
res.sendFile(__dirname+req.path);
};
});
sio.sockets.on("connection", function(socket) {
SESSION = socket.request.session
console.log("The SESSION variable on socket connection:");
console.log(SESSION); //<<<<<<<<<<<< UNDEFINED
socket.on("get session status",function(){
socket.emit("session status",{ SESSION:SESSION }); // <<<<< EMPTY OBJECT
})
});
server.listen(80);
The accepted answer at the second link works fine for me:
app.js:
var session = require("express-session");
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
var sessionMiddleware = session({
secret: "keyboard cat",
resave: true,
saveUninitialized: true
});
app.use(sessionMiddleware);
app.use(function(req, res) {
req.session.name = "THE NAME";
res.sendFile(__dirname + "/index.html");
});
io.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res, next);
});
io.on("connection", function(socket) {
console.log('socket.io connection');
console.dir(socket.request.session);
// above outputs:
// socket.io connection
// { cookie:
// { path: '/',
// _expires: null,
// originalMaxAge: null,
// httpOnly: true },
// name: 'THE NAME' }
});
server.listen(8000);
index.html:
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost:8000');
</script>
Perhaps there is an issue with timing where the session data isn't saved to Redis yet when the socket.io connection is made? You might try delaying the socket.io connection at least until the page is fully rendered (window.onload).

express-session, read vars on socket.io interaction

I am making a chat for customers of a web page, with express.js, and socket.io, and I'm trying to manage sessions with express-session, my problem is, how do I read the session values on the socket.
Here's part of my code.
Thanks for the help :)
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var bodyParser = require('body-parser');
var session = require('express-session');
var shortid = require('shortid');
app.use(express.static('public'));
app.use( session({
secret: 'dont move',
resave: true,
saveUninitialized: true
})
);
app.use( bodyParser.urlencoded({ extended: true }) );
app.post('/chat', function(req, res){
var sess = req.session;
sess.ssid = shortid.generate();
res.render('chat', { name: "name" });
});
io.on('connection', function(socket){
socket.on('chat message', function( data ){
if( data.msg.trim() != '' ){
console.log('message: ' + data.msg);
//Here I want to read the ssid session var, so I can manage many chats at the same time
}
});
socket.on('disconnect', function(){
console.log('Hey, someone disconected!');
});
});
http.listen(3000, function(){
console.log('listening on *:3000');
});
You can configure socket authorization
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var bodyParser = require('body-parser');
var session = require('express-session');
var shortid = require('shortid');
app.use(express.static('public'));
app.use( session({
secret: 'dont move',
resave: true,
saveUninitialized: true
})
);
app.use( bodyParser.urlencoded({ extended: true }) );
app.post('/chat', function(req, res){
var sess = req.session;
sess.ssid = shortid.generate();
res.render('chat', { name: "name" });
});
var util = require('util';
var connect = require('connect');
var parseSignedCookie = connect.utils.parseSignedCookie;
io.configure(function () {
io.set('authorization', function (data, callback) {
if(data.headers.cookie) {
data.cookie = cookie.parse(data.headers.cookie);
data.sessionId = parseSignedCookie(data.cookie['ssid'], 'dont move');
}
callback(null, true);
});
io.on('connection', function(socket) {
var sessionId = socket.handshake.sessionId;
socket.on('chat message', function( data ){
if( data.msg.trim() != '' ){
console.log('message: ' + data.msg);
// NOW YOU CAN USE `sessionId`
}
});
socket.on('disconnect', function(){
console.log('Hey, someone disconected!');
});
});
});
http.listen(3000, function(){
console.log('listening on *:3000');
});

connect-redis and express-session results in req.session undefined

I'm trying to get express-session to store the session in Redis, but it doesn't seem like it wants to save. When I revert back to default session store it works flawlessly. The Redis daemon is hosted on a Vagrant VM with default configuration, and the app is able to connect to it, although it doesn't want to save sessions to it.
Here's my code:
var express = require('express');
var glob = require('glob');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var compress = require('compression');
var methodOverride = require('method-override');
var session = require('express-session');
var passport = require('passport');
var redis = require('redis');
var RedisStore = require('connect-redis')(session);
var auth = require('./passport');
var flash = require('connect-flash');
module.exports = function(app, config) {
app.set('views', config.root + '/app/views');
app.set('view engine', 'jade');
// app.use(favicon(config.root + '/public/img/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(cookieParser());
app.use(session({
store: new RedisStore({
host: config.redis.host,
port: config.redis.port
}),
secret: config.secret,
saveUninitialized: true,
resave: false
}));
/*app.use(session({
secret: config.secret,
saveUninitialized: true,
resave: true
}));*/
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
app.use(compress());
app.use(express.static(config.root + '/public'));
app.use(methodOverride());
var controllers = glob.sync(config.root + '/app/controllers/*.js');
controllers.forEach(function (controller) {
require(controller)(app);
});
app.use(function (req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
if(app.get('env') === 'development'){
app.use(function (err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err,
title: 'error'
});
});
}
app.use(function (err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {},
title: 'error'
});
});
};
As you can see, I got the default one commented out, and if I remove those comments and comment out the redis store, it works as it should. Any clues why this is happening? I'm not getting a single error.
You probably have some connection errors, but connect-redis does not output them to console (see connect-redis source code). To see them you can create a separate module that creates a client instance and pass it to RedisStore constructor:
// redisClient.js
var redis = require('redis');
var redisClient = redis.createClient('localhost', 6379); // replace with your config
redisClient.on('error', function(err) {
console.log('Redis error: ' + err);
});
module.exports = redisClient;
Redis client emits also other event that may be helpful in debugging - see node-redis docs
// your code
var redisClient = require('./redisClient.js`);
(...)
app.use(session({
store: new RedisStore({
client: redisClient
}),
secret: config.secret,
saveUninitialized: true,
resave: false
}));

Resources