Link socket.io ID with express and passport - express

The goal of my simple try is to display online user list. I mean display not socket.io ID but display user profile. When authorized user connects to my server, open socket.io channel, it is required to get his profile and send message to other connected user that new user (Name, email, etc) has being connected. I saw many examples how to do it within authorization, but it doesn't handle a disconnect. What i want to do and what i can't do in pseudocode:
var io = require("socket.io").listen(server);
io.set("authorization", function(data, callback){
// ... some code...
callback(null, true);
});
io.sockets.on('connection', function (socket) {
var UserProfile = passport.getUserProfile(socket.id)
io.sockets.emit('user_connected', {UserProfile: UserProfile, socketID: socket.id});
io.sockets.on('disconnect', function (socket) {
io.sockets.emit('user_disconnected', {socketID: socket.id});
});
});
This is a pseudocode!
My stack is overflowed. I just want to link socket.io ID and passport account together within connection. How can i do it?

I got the same problem, and my solution is the following (hopefully someone gets a better idea):
i add the username to the render call (using jade):
res.render('chatroom', {username: req.user.username});
right after connecting to socket on the client (io.connect), i emit a
message to the server, with the username as parameter, using the connect event on the client (socket.on('connect', function (data) { ... });
on the server, i store the username in an object (clients[socket.id]
= username)
after that, i get the username in every socket message by accessing
the clients object

Related

bind socket.io id to user id without sessions (express, passport JWT)

I'm developing RESTful API on express, with JWT and passport for authorization. I want to implement socket.io connection for notification and signalling purposes (WebRTC session establishing). I don't want to implement standard session management, don't want to deal with cookies, but somehow I have to be able to address particular user via socket. I have event handling in all my routes, so app is aware of auth-ed requests and corresponding user ids. One approach(probably) is to create socket io group with user id, add socket to this group and emit there. (Engaging reconnection handling and checking socket existence on every subsequent request - that's way overcomplicated). I guess there should be a better approach. I also use Redis, so I can leverage that in this scheme. Any suggestion is appreciated, thank you
Well, I managed to solve that in such a manner.
Server:
import jwt from 'jwt-simple'; //I'm using ES6/Babel
module.exports = function(app) {
var io = app.get('io'); //Import io any possible way,
//here I do it like so because I set
//app.set('io', io) in my index.js
var user_id;
io.on('connection', socket => {
// Recieve encoded token from client, decode and find user id
// To do - check againt database
socket.on('auth', token => {
if (token) {
var decoded = jwt.decode(token, 'secret')
user_id = decoded.sub
socket.emit('auth', user_id);
}
})
// Join room proposed by client - user id string
socket.on('room', room => {
socket.join(room)
console.log('Server joined room...', room)
//emit message to user id from anywhere in the app
io.sockets.in(user_id).emit('message', 'what is going on, party people?');
})
})
}
client:
var token = localStorage.getItem('token');
var socket = io();
socket.on('connect', data => {
socket.emit('auth', token);
socket.on('auth', user_id => {
socket.emit('room', user_id);
})
})
Now to address specific user I can always emit to room id equal to user id, provided that user has got credentials. Even after browser refresh.
Photo: the left client has valid token in localStorage, the right one doesn't

Session Property not saving in session store when retrieving from socket.io

What I am trying to accomplish is to store the username of the current user in the session, such that it can be retrieved by socket.io.
Currently, I have the client sent its sessionId to the socket.io endpoint (via io.connect):
var socket = io.connect('http://localhost?sessionId=' + sessionid);
and then on the server side, I have:
io.use(function(socket, callback) {
if (socket && socket.handshake && socket.handshake.query && socket.handshake.query.sessionId) {
var sessionId = socket.handshake.query.sessionId;
sessionStore.get(sessionId, function(error, session) {
socket.handshake.sessionId = sessionId;
if (error) {
callback('Could not set session id with socket io authorization handshake!', false);
} else if (!session) {
callback('There was no session found during socket io authorization handshake!', false);
}
console.log(session);
socket.session = session;
callback(null, true);
});
} else {
callback('No sessionId value was provided in query string to socket io connect from client', false);
}
});
This correctly retrieves the session from the client-provided sessionid (assuming the id exists, of course).
Now, what I want to do is be able to is get the passportjs-managed username accessible from within the socketio handlers, via the session.
So, to start with, I have this code in the router controller for this endpoint:
router.get('/', Authentication.redirectIfNotAuthenticated, function(req, res) {
req.session.user = req.user;
console.log(req.session);
res.locals.sessionId = req.session.id;
res.render('mypage', { title: 'This is my page', user: req.user });
});
That console.log(req.session) does show me that the req.user has been correctly inserted into the session object and is visible. Authentication.redirectIfNotAuthenticated is using passportjs and is really simple (code included incase it is the issue):
redirectIfNotAuthenticated : function(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.redirect('/');
}
So...we know that the user is being inserted as a property into the session object...but when I hit one of my socketio methods:
socket.on('init', function(info) {
console.log(socket.session);
...
});
That console.log(socket.session) does not show the session that I modified in the route, but shows the "original" session object - as though I never added anything to it at all! I do know that socket.on('init'... is being called after my io.use(function.... code as detailed above (where the session is pulled from the sessionStore and saved in the socket object).
I just haven't been able to track down why the session, as stored in socket.session (as retrieved from the session store), isn't mirroring the latest changes made to the session in the router.
If anyone has any suggestions, or reasons why what I expect to see is incorrect, I would be happy to hear them!
A session scopes a connection between a dedicated client browser tab and a server.
As you did not explain why you want to store the username in the session I can base my answer only on guesses:
If you want to remember the username for later use in the session store it on the client side.
If you want to declare the user name (and other variables) to other users in the communication space the easiest way to do that is to store the data in a database (mind that databases can be in-memory, which works out to be quick). You could also fittle with files - but if you run mutliple users you will have to switch to a database anyway...
In any way there is no need to extend the scope of a session over multiple clients.

use socket.io in express best practice

I'm doing a system using expressjs + socket.io(with SessionSocket plugin) + mongoosejs.
The server was build like this:
var mongoose = require('mongoose'),
connect = require('connect'),
express = require('express'),
http = require('http'),
io = require('socket.io');
// hold cookieParser and sessionStore for SessionSocket
var cookieParser = express.cookieParser('your secret sauce'),
sessionStore = new connect.middleware.session.MemoryStore();
// create express server
var app = express();
// config express server
require('./config/express')(app, config, cookieParser, sessionStore);
// Express3 requires a http.Server to attach socke.io
var server = http.createServer(app);
// attach socket.io
var sio = io.listen(server);
sio.set('log level', 2);
// create SessionSocket
var SessionSockets = require('session.socket.io'),
sessionSockets = new SessionSockets(sio, sessionStore, cookieParser);
// setup SessionSocket
sessionSockets.on('connection', function(err, socket, session) {
socket.join(session.user._id);
socket.emit('message', {
title: "welcome",
msg: "welcome to selink"
});
});
// config express server route
app.get('/some-action', **SomeMongooseFunction**);
// start server
server.listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
In the express route handler("SomeMongooseFunction", defined in other file), after processed user action(query, modify DB etc.), I want sent a real-time notification to the user or other online user. so I thing I must pass "sio" object into the "SomeMongooseFunction", the example above has only one route, but actually I have dozens of routes, It gonna looks ugly if I do so.
or in the setup SessionSocket part, I think could paste the sio into the user's session. but, I'm not sure this way is right or not.
so, is there any other way to get sio reference in the express handler? (it will be nice if I can refer sio like a global variable, but global is evil...)
thanks for any ideas.
You're storing user ID from session into Socket.IO rooms manager. Now you have somehow get the target's user ID
and send the event message for this user.
For your case, you are using notification entity so lets do it:
//client side - send a notification for a specific user.
var note = {
from: userID, //sender
target: id, //receiver
type: type //type of notification
}
socket.emit('notification', note);
target field is the target user ID, this user will receive the notification status.
type here is just for illustrate if you wanna to handle more than one type of notifications.
socket.emit('notification', note); this part is the most important one. It will send the note JSON object
to notification event. This event must be registered on you server side Socket.IO page;
//server side - receive JSON data on notification event
socket.on('notification', function(data) {
io.sockets.in(data.target).emit('notification', data);
});
NOTE: I use 'io' instead of 'sio'.
notification event is registered and will catch all client messages directed to it. data is the
JSON object we sent before, so io.sockets.in().emit()
Once again on client. We must catch all notification event.
//client side - the final destination
socket.on('notification', function(data) {
//DO SOME JQUERY STUFF TO ALERT THIS TARGET USER FROM THE MESSAGE RECEIVED.
console.log("User: " + data.from + " sent you this notification");
});
The steps we can conclude are:
Sender user sends a message for notification event on server-side that contains target user
Server gets the message and now knows which event and target must be receive the message.
Server sends the message to the correct target on specified socket.io event.
You can use this analogy for any case you have to send Socket.IO messages for a specific user.

disconnect client from server side signalr

I'm using SignalR 1 with MVC4 C# web application with form authentication.
I have a code in my layout page in JavaScript :
$(documnet).ready(function(){
connect to hub code ...
})
I want to disconnect a user form the hub and start connect again after he does a login and validate ok.
I want to do it from server side inside my account controller and method :
public ActionResult LogOn(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (System.Web.Security.Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
....here , disconnect from hub
....to make the user reconnect
}
The reason I want to do it is because SignalR throws an error if user changed to authenticated after login and the connection remains . The error is:
The connection id is in the incorrect format.
You cannot stop and start SignalR connections from the server. You will need to call
$.connection.hub.stop(); //on the client before the user attempts to log on and then call
$.connection.hub.start(); //after the log on attempt has completed.
One way you could do what you ask is to write a disconnect event on your client that the server can call through SignalR. Maybe something somewhat like this:
myHub.client.serverOrderedDisconnect = function (value) {
$.connection.hub.stop();
};
Then, on the server, something like this:
Clients.Client(Context.ConnectionId).serverOrderedDisconnect();
If someone is still looking for solution(SignalR version 2.4.1):
GlobalHost.DependencyResolver.Resolve<ITransportHeartbeat>().GetConnections().First(c => c.ConnectionId == "YourId").Disconnect();
Try controlling everything from javascript. The following is a logout example, login would be similar.
From http://www.asp.net/signalr/overview/security/introduction-to-security:
If a user's authentication status changes while an active connection
exists, the user will receive an error that states, "The user identity
cannot change during an active SignalR connection." In that case, your
application should re-connect to the server to make sure the
connection id and username are coordinated. For example, if your
application allows the user to log out while an active connection
exists, the username for the connection will no longer match the name
that is passed in for the next request. You will want to stop the
connection before the user logs out, and then restart it.
However, it is important to note that most applications will not need
to manually stop and start the connection. If your application
redirects users to a separate page after logging out, such as the
default behavior in a Web Forms application or MVC application, or
refreshes the current page after logging out, the active connection is
automatically disconnected and does not require any additional action.
The following example shows how to stop and start a connection when
the user status has changed.
<script type="text/javascript">
$(function () {
var chat = $.connection.sampleHub;
$.connection.hub.start().done(function () {
$('#logoutbutton').click(function () {
chat.connection.stop();
$.ajax({
url: "Services/SampleWebService.svc/LogOut",
type: "POST"
}).done(function () {
chat.connection.start();
});
});
});
});
You can the hub context to abort() the connection directly in .Net Core
Context.Abort();
See the method below
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.signalr.hubcallercontext.abort
Try this:
public ActionResult LogOn(LoginModel model, string returnUrl) {
if (ModelState.IsValid) {
if (System.Web.Security.Membership.ValidateUser(model.UserName, model.Password)) {
FormsAuthentication.SetAuthCookie(model.UserName, false);
connection.Stop();
}
}
Assuming your connection handle is connection. The challenge is accessing a handle to your connection object in your Action Method.
Copy and paste the following function into your Hub
Use HttpContext.Current.Response.End();
to force the client to disconnect in your hub

socket.io authentication after socket established

I'm working on a small multiplayer game. I'd like to introduce authentication. I'm using Node.js and Socket.io.
When the user arrives that the main page - I want them to join the game whether they are logged in or not - but they will be unable to do anything within it (only watch).
How could I then go about authenticating the user on the already open socket?
Could I maintain the authentication still if they left the site and came back? Can you pass a cookie through a web socket?
EDIT
To further my question. One of the possible thoughts I've had is to provide the websocket connection, then when they try to login in, it passes username and password as a message to the websocket.
client.on('onLogin', loginfunction);
I could then take the username and password, check against the database, then take the session ID of the socket and pass it somewhere to say that session is authenticated to that user.
Is this secure? Could I still implement a cookie on the socket so they could come back? Is there any way within socket.io of stating that the socket is now authenticated instead of manually checking on each message received?
Cheers
This isn't actually too hard, but you're approaching it the wrong way. A couple things:
You cannot set a cookie with socket.io; you can, however, get the cookie values of any connected client at any time. In order to set a cookie, you will have to send a new http response, meaning the user must first send a new http request (aka refresh or go to a new page, which it sounds is not a possibility for you here).
Yes: socket.io is secure (to the extent that any transmitted data can be).
As such, you can do the following:
On the user's initial connection, create a cookie with a unique session ID, such as those generated from Express's session middleware. You will need to configure these not to expire on session end though (otherwise it will expire as soon as they close their browser).
Next you should create an object to store the cookie session IDs. Each time a new connect.sid cookie is set, store in your new object with a default value of false (meaning that the user has been authenticated by session, but not by logon)
On the user's login, send a socket emit to the server, where you can then authenticate the login credentials, and subsequently update the session id object you created to read true (logged in) for the current socket id.
Now, when receiving a new http request, read the cookie.sid, and check if its value in your object is true.
It should look something like the following:
var express = require('express'),
http = require('http'),
cookie = require('cookie');
var app = express();
var server = http.createServer(app);
var io = require('socket.io').listen(server);
app.use(express.cookieParser());
app.use(express.session({
secret: 'secret_pw',
store: sessionStore,
cookie: {
secure: true,
expires: new Date(Date.now() + 60 * 1000), //setting cookie to not expire on session end
maxAge: 60 * 1000,
key: 'connect.sid'
}
}));
var sessionobj = {}; //This is important; it will contain your connect.sid IDs.
//io.set('authorization'...etc. here to authorize socket connection and ensure legitimacy
app.get("/*", function(req, res, next){
if(sessionobj[req.cookies['connect.sid']]){
if(sessionobj[req.cookies['connect.sid']].login == true){
//Authenticated AND Logged in
}
else{
//authenticated but not logged in
}
}
else{
//not authenticated
}
});
io.sockets.on('connection', function(socket){
sessionobj[cookie.parse(socket.handshake.headers.cookie)['connect.sid'].login = false;
sessionobj[cookie.parse(socket.handshake.headers.cookie)['connect.sid'].socketid = socket.id;
socket.on('login', function(data){
//DB Call, where you authenticate login
//on callback (if login is successful):
sessionobj[cookie.parse(socket.handshake.headers.cookie)['connect.sid']] = true;
});
socket.on('disconnect', function(data){
//any cleanup actions you may want
});
});
Chris, I'm won't be able to answer since I'm not an expert on socket.io, but I can maybe try to point you in another direction that can help you - and take away some development time.
But first, a disclaimer: I work for Realtime.co and am not trying to do any sort of advertising. I work closely with developers and I'm just trying to help you by providing you an out-of-the-box solution for your problem. Also, being a gamer, I can't stay away from trying to help people getting their games out there!
Realtime uses an authentication/authorization layer in which you can provide user read/write permissions to channels. When users enters the website you can give them read only permissions to the game channel and once they login, you can then give them write permissions. This can be easily done by doing an authentication post and reconnecting to the server (it can all be done client side). I would do it server-side, though, to increase security.
Realtime has a Node.js API so you can easily integrate it with your server. Since it also has APIs for many other platforms (including mobile) and they all work the same way, you can actually have your game working in multiple platforms over the same communication layer, while having full control over channels.
Thanks for reading.
Edit:
You can read the documentation here for more info: http://docs.xrtml.org/
Socket.io has an option to pass extraHeaders. One can use that to pass a token from the client. The server would use the desired authentication algorithm to decrypt the token and get the user_id.
socket.js
import io from 'socket.io-client';
export const socket = io('https://remote-url');
export const socketAuth = () => {
socket.io.disconnect(); //This uses the same socket and disconnect with the server.
socket.io.opts.extraHeaders = {
'x-auth-token': JSON.parse(window.localStorage.getItem('auth-token')),
};
socket.io.opts.transportOptions = {
polling: {
extraHeaders: {
'x-auth-token': JSON.parse(window.localStorage.getItem('auth-token')),
},
},
};
socket.io.open(); //Opens up a new connection with `x-auth-token` in headers
};
client.js
import { socket, socketAuth } from 'utils/socket';
socket.on('unauthenticated-messages', () => {
someAction();
});
//After user logs in successfully
socketAuth();
socket.on('authenticated-messages', () => {
someAction();
});