I have a Node.js server using express framework with a session system. The program is a simple 1vs1 game. In order to play, a user can create a room and an other user can join it so the game can start. The room ID is stored into the user's session.
If you take a look at the following code, you'll see how I manage to access express sessions in a "socket code". This is great, it works. However, I cannot modify values in the session object.
var express = require('express');
var session = require('express-session');
var sessionMiddleware = session({
secret: 'my secret',
resave: false,
saveUninitialized: false
});
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io').listen(server);
app.use(sessionMiddleware);
io.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res, next);
});
io.on('connection', function(socket) {
// some stuff
socket.on('disconnect', function() {
socket.request.session.room = undefined; // this is not working
});
});
server.listen(8080);
Is there a way to do what I want or should I rethink the way rooms are managed ?
Thank you
Related
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);
I have application which use node.js and socket.io.
I would like store some information in session. I made example. But my code doesn't work. When I run my script and refresh page I see:
NaN
2
Next, when I refresh page again I can see
NaN
2
NaN
2
So session is not stored. How can I fix my code ?
var Session = require('express-session'),
SessionStore = require('session-file-store')(Session);
var session = Session({ secret: 'pass', resave: true, saveUninitialized: true });
var config = require("./config.json");
var express = require('express');
var app = express();
var socketio = require('socket.io');
var server = app.listen(3000, function(){
console.log('Start')
});
var io = require('socket.io').listen(server);
app.use(session); // session support
app.get('/', function (req, res) {
req.session.ziom = 1;
req.session.save();
console.log('dec');
});
var http = require('http');
var ios = require('socket.io-express-session');
io.use(ios(session));
io.sockets.on('connection', function(socket){
console.log(parseInt(socket.handshake.session.test));
socket.handshake.session.test =2;
socket.handshake.session.save();
console.log(parseInt(socket.handshake.session.test));
});
I dont't understand this error because I copy-past your code and everything* work fine !
*I can't see all your code/project, I don't know if you have an index.html file who start connexion event with the node server. I had this file with minimum code for test.
Change in my app.js is res.sendfile('./index.html');
See all my code below for comparison.
app.js
var Session = require('express-session'),
SessionStore = require('session-file-store')(Session);
var session = Session({ secret: 'pass', resave: true, saveUninitialized: true });
var express = require('express');
var app = express();
var socketio = require('socket.io');
var server = app.listen(3000, function(){
console.log('Start')
});
var io = require('socket.io').listen(server);
app.use(session); // session support
app.get('/', function (req, res) {
req.session.ziom = 1;
req.session.save();
console.log('dec');
res.sendfile('./index.html');
});
var http = require('http');
var ios = require('socket.io-express-session');
io.use(ios(session));
io.sockets.on('connection', function(socket){
console.log(parseInt(socket.handshake.session.test));
socket.handshake.session.test =2;
socket.handshake.session.save();
console.log(parseInt(socket.handshake.session.test));
});
index.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script src="https://cdn.socket.io/socket.io-1.3.5.js"></script>
<script>
var socket = io();
</script>
</body>
</html>
Sincerely hope that helps.
I assume you're trying to open the socket.io connection using an HTML file similar to the bootstrap file in the tutorial (second html snippet after this link)?
With a small change, your code worked for me, where working means you get
NaN
2
2
2
on the console with two page refreshes.
In order to get this, I created the boostrap file relative to the index.js file (a copy of your code above) at views/socket.html, and ensured this was served by the express app by adding these lines to the handler for the '/' route:
require('fs').readFile('views/socket.html', function (err, data) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data, "utf8");
res.end();
});
Then visited http://localhost:3000 twice.
It is required for this file to be served by the express app in order for the session cookie to be stored; so that the socket.io connection knows to use the same cookie to be linked to the same session at the next refresh.
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).
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);
I was thinking to implement a role/auth model on top of socket.io connections. There is a lot out there about this, especially for token authentication. But what about what I am attaching below? What would be wrong with this approach?
The idea with the code below is to give access to socket.io connections only to specific roles.
var role = true;
module.exports = function (io) {
if (role == true){
'use strict';
io.on('connection', function (socket) {
socket.on('chat message', function(msg){
io.emit('chat message', msg);
});
});
};
};
You can use Passport.socketio to authentify your socket connections. It works really well with express. Here is a sample app :
var express = require('express');
var passport = require('passport');
var passportSocketIo = require("passport.socketio");
var cookieParser = require('cookie-parser');
var session = require('express-session');
var app = express();
var RedisStore = require('connect-redis')(session);
var sessionStore = new RedisStore({
host: 'localhost',
port: 6379,
db: 2,
});
app.use(session({
store: sessionStore,
secret: 'YOUR_SECRET_HERE'
}));
app.use(passport.initialize());
app.use(passport.session());
var io = require('socket.io').listen(http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
}));
io.use(passportSocketIo.authorize({
cookieParser: cookieParser,
secret: 'YOUR_SECRET_HERE',
store: sessionStore,
}));
In your case, you'd have to add the roles to your passport users. The user info can then be accessed in socket.request.user
io.sockets.on('connection', function(socket) {
var user = socket.request.user;
if (user.role == true){
//do something
}
});
Please note that you have to use a sessionStore, local storage doesn't work with passport. Refer to the Git page above for different possibilities.