passport session with websocket server library - node.js

Could passport session be expected to work with a web socket library( https://github.com/websockets/ws )? We have a regular passport authentication setup: but the req.session.user sometimes shows up correctly in the websocket request and sometimes it does not.
We are initializing the websocket like this:
this.wss = new WebSocketServer({
server: this.server, // This is the express app
verifyClient: function(info, done) {
self.session_parser(info.req, {}, function() {
console.log('VERIFY ', info.req.session);
// allow connection only if session is valid and a user is logged in
done(info.req.session && info.req.session.passport &&
info.req.session.passport.user &&
info.req.session.passport.user.id);
});
}
});
The problems is that info.req.session.user shows up correctly for some web socket requests and sometimes it does not in the verifyClient function. req.session always shows up correctly.

It is in dev env?
If you use memorystore session it cleaned after app reload. You can switch to session-file-store but it usually don't store on res.redirect()
In other cases you must check is session don't expire on server.

Related

Is node.js socket.io-client supposed to handle set-cookie

Is the node.js socket.io-client supposed to automatically handle cookies? That is, for all Set-Cookie response headers, is it supposed to pass back the corresponding Cookie headers during the handshake?
The reason I'm asking is because I have a proxy (the cloud foundry gorouter) between my client and 3 server instances. The socket.io server is appropriately setting two cookies (JSESSIONID and VCAP_ID) on the response and I need the client to send them back appropriately so that affinity is kept by the gorouter. I am currently getting connect failures due to a "transport error" when multiple instances of the server are running, but the problem goes away when I have a single server instance running.
Thanks in advance,
Keith
If you want to access cookies in socket.io check out the following.
http://socket.io/docs/server-api/#namespace#use(fn:function):namespace
var io = require('socket.io')();
io.on('connection', function(socket){
socket.to('others').emit('an event', { some: 'data' });
});
Additionally check out this post on how to do authentication in socket. Socket.IO Authentication
Yes, I did get it to work, but the only node module I could get to work at the time was 'ws' as follows:
var WebSocket = require('ws');
var webSocketUrl = ""wss://" + ...
var opts = { headers: { Cookie: 'JSESSIONID=1; __VCAP_ID__='+vcapID} };
var socket = new WebSocket(websocketUrl,opts);
-- Keith

Authentication with Node/Express/Socket.IO

I have a node/socket.io/express server that's connected to a HTML file (like so). So visiting the web address connects you to the server. I am trying to set up a system where by, said server is being run on multiple computers at a time and by way of some sort of username and password authentication, visiting the webpage with specific credentials connects you to one of the computers with those same credentials running the server.
Ive seen mention of "Redis" from previous similar questions but they are pretty old and im wondering if there is a newer or better way of achieving this.
You won't find a lot of up-to-date documentation since Express 4 is kind of new, so let me try to remedy that here :
Authentication in Express 4.x and Socket.IO 1.x
Let's start with a confusion I think you're making:
What is Redis?
Redis is a data structure engine. It allows you to store key/values pairs, nothing more (In this context). The only thing it can do for you when building your authentication system is storing the data, user info, session ids, etc. In your case, you can share a store between multiple machines, the same way you'd share a database, or a text file.
Redis
Authenticate user to node/express server
One of the ways you can do that is by using passport. Passport is a middleware dedicated to authentication on Node.js. It is made for use with Express and relatively easy to setup. There is an excellent tutorial series on how to setup passport with your express application, so I won't detail this part, please take the time to go through the series, it's invaluable knowledge.
Here's the link to the first part, which is the one I'll focus on for the next step.
Add socket.io to the mix
Socket.io doesn't have access to the session cookies that you create in part 1. To remedy that, we will use the passport-socketio module.
Passport-socketio requires a local session store, as opposed to a memory store. This means we need some way to store the session data somewhere, does that ring a bell?
Exactly, Redis.
You can try other stores, like mongoDB or MySQL, but Redis is the fastest.
In this example, I'll assume that your express app and passport are already operational and will focus on adding socket.io to the app.
Setup :
var session = require('express-session'); //You should already have this line in your app
var passportSocketIo = require("passport.socketio");
var io = require("socket.io")(server);
var RedisStore = require('connect-redis')(session);
var sessionStore = new RedisStore({ // Create a session Store
host: 'localhost',
port: 6379,
});
app.use(session({
store: sessionStore, //tell express to store session info in the Redis store
secret: 'mysecret'
}));
io.use(passportSocketIo.authorize({ //configure socket.io
cookieParser: cookieParser,
secret: 'mysecret', // make sure it's the same than the one you gave to express
store: sessionStore,
success: onAuthorizeSuccess, // *optional* callback on success
fail: onAuthorizeFail, // *optional* callback on fail/error
}));
Connect-redis is a session store package that uses redis (in case the name isn't obvious).
Final step :
function onAuthorizeSuccess(data, accept){
console.log('successful connection to socket.io');
accept(); //Let the user through
}
function onAuthorizeFail(data, message, error, accept){
if(error) accept(new Error(message));
console.log('failed connection to socket.io:', message);
accept(null, false);
}
io.sockets.on('connection', function(socket) {
console.log(socket.request.user);
});
The user object found in socket.request will contain all the user info from the logged in user, you can pass it around, or do whatever you need with it from this point.
Note : This setup will be slightly different for Socket.IO < 1.x

Socket.IO on Heroku does NOT work without SSL

I have a chat server setup as such:
var port = Number(process.env.PORT || 5000);
var app = require('http').createServer(handler)
, io = require('socket.io').listen(app, {'log level':1, 'match origin protocol':true})
, fs = require('fs')
io.set('authorization', function (handshakeData, callback) {
console.log(handshakeData);
callback(null, true);
});
and then I handle some events:
io.sockets.on('connection', function(socket) {
socket.emit('handshaken', {id:socket.id}); // for HTML clients
socket.on('subscribe', function(roomId) {
doSubscribe(socket, roomId);
});
socket.on('unsubscribe', function(roomId) {
doUnsubscribe(socket, roomId);
});
socket.on('chat', function(data) {
doChat(data);
});
});
The client is on a different domain.
When I use the chat server via https, then everything is working fine. All the events are received. However, when I use http, I can see that the client can receive the 'handshaken' event, but nothing else is sent or received.
I wonder if this has anything to do with the socket.io authorization not working properly with non ssl connection.
However, in local environment, I can still use non ssl http://localhost:5000 as the chat server url without any issue. Is it also possible that this is an issue with Heroku?
UPDATE 1: After some investigation, if I use http url for the chat server, the server can emit to the client. The client can connect to the server, but cannot emit anything to the server (the server does not receive any emit).
Update 2: Some further investigations revealed that the chat server, under http, does received an emit, but only 1 emit. Any emit after that is not received.
It turned out that Sophos antivirus for Mac is the culprit here. After I disabled all web protection, my chat app works fine.
The interesting point here is that Sophos only targets Chrome browser, as Firefox and Safari work without any problem.

integration socket.io - express 4 ,same port and security session

I'm working with mean.js and I need to have some real time features in my app, to accomplish that I'm going to use socket.io library.
Here is my idea on how to integrate and still have a good structure in the app.
Mean is using a server.js file, that is the one that do a lot of configurations, so I want to do the following:
// Expose app
exports = module.exports = app;
// Add my reference to the socketServer
var io = require('/socketServer')(app);
The file '/socketServer.js' is going to be my starting point and my configuration point of my socket, could looks something like this:
var http = require('http');
var socketio = require('socket.io');
module.exports = function(express){
var server = http.Server(express);
var io = socketio(server);
io.path('/');
io.on('connect', function(socket){
socket.emit('connected', {msg: 'You are connected now.'});
socket.on('upvote', function(data){
socket.emit('upvoteR', 'newConnected');
socket.broadcast.emit('upvoteR', 'newCOnnected');
});
});
server.listen(8080);
return io;
};
I feel like could be useful for me separate the server default config, of my socket config, and use it file (socketServer.js) as my starting point to develop all my sockets logics injecting the dependencies I want.
I don't know if is out there a better approach to this problem, or some structure best practices that I should follow or inconveniences of doing this.
So besides this structure, this are other doubts:
How to use sockets and express server in the same port?
Seems like, with express 4 I'm not able to link the express server with socket, because express 4 server does not inherit any more of httpServer of node.js, so now I have to do a server.listen(socketPort) and if I use the same app.port of mean.js this just is an EADDRINUSE error. Is still possible to have it working in the same port ?
How to use express session to authenticate each socket connection? if not possible, what's the better approach ? An example or a document reference would be nice for me.
thanks in advance.
I would like to share my solution just in case someone in the future has the same requirement that I had.
How to authenticate each socket connection base on express session information.
First I configure express to use passport.js library the following way:
// CookieParser should be above session
var cp =cookieParser;
app.use(cp());
// Express MongoDB session storage
var mStore = new mongoStore({
db: db.connection.db,
collection: config.sessionCollection
});
app.use(session({
secret: config.sessionSecret,
store: mStore
}));
// use passport session
app.use(passport.initialize());
app.use(passport.session());
So far is the normal implementation of passport over express. be sides this configuration I added passport-socket.io.js to my project. This is my working configuration:
var server = http.Server(app);
var io = IO(server);
io.use(
function(socket,next){
passportSocketIo.authorize({
cookieParser: cp,
key: 'connect.sid', // the name of the express cookie
secret: config.sessionSecret, // the session_secret to parse the cookie
store: mStore, // mongo session storage
success: onAuthorizeSuccess, // *optional* callback on success
fail: onAuthorizeFail, // *optional* callback on fail/error
})(socket, next);
}
);
app.io=io;
server.listen(config.port);
Where "onAuthorizeSuccess" and "onAuthorizeFail" are functions to allow the conections and develop the sockets logics.. well,with this my socket.io connection is authenticated with my passport session information and if the user is not logged the socket would not connect..
And if we need some authorization logic based on user roles, the passport.socketio creates a socket.request.user where you can find yours users roles to use in your roles sockets logics..

Securing Socket.io

I am using a Node.js based https server that authenticates using HTTP Basic (which is fine as the data are sent over the SSL encrypted connection).
Now I want to provide a Socket.io connection which should be
encrypted and
for authenticated users only.
The question is how to do this. I already found out that I need to specify { secure: true } in the client JavaScript code when connecting to the socket, but how do I force on the server-side that socket connections can only be run over SSL, and that it works only for authenticated users?
I guess that the SSL thing is the easy part, as the Socket.io server is bound to the https server only, so it should run using SSL only, and there should be no possibility to run it over an (additionally) running http server, right?
Regarding the other thing I have not the slightest idea of how to ensure that socket connections can only be established once the user successfully authenticated using HTTP Basic.
Any ideas?
Edit: Of course OP is right in their other answer; what's more, with socket.io >1.0 you might use socket.io-express-session.
Original answer:
Socket.io supports authorization via the io.set('authorization', callback) mechanism. See the relevant documentation: Authorizing. Here's a simple example (this authenticates using a cookie-based session, and a connect/express session store -- if you need something else, you just implement another 'authorization' handler):
var utils = require('connect').utils;
// Set up a session store of some kind
var sessionId = 'some id';
var sessionStore = new MemoryStore();
// Make express app use the session store
app.use(express.session({store: sessionStore, key: sessionId});
io.configure(function () {
io.set('authorization', function (handshakeData, callback) {
var cookie = utils.parseCookie(handshakeData.headers.cookie);
if(!(sessionId in cookie)) {
return callback(null, false);
}
sessionStore.get(cookie[sessionId], function (err, session) {
if(err) {
return callback(err);
}
if(!('user' in session)) {
return callback(null, false);
}
// This is an authenticated user!
// Store the session on handshakeData, it will be available in connection handler
handshakeData.session = session;
callback(null, true);
});
});
});
Although the answer by Linus is basically right, I now solved it in a more easy way using the session.socket.io - which basically does the same thing, but with way less custom-code to write.
Setting the socket to be secured is done by:
setting the secure flag - as you wrote ({secure: true})
OR by using https protocol when creating the server (instead of http) - which will set the secure flag to be true automatically
Good lib that simplify the authentication process is: https://github.com/auth0/socketio-jwt

Resources