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.
Related
I'm building a FeathersJS service behind an authentication very similar to the messages service that is part of the FeathersJS demo chat app: https://github.com/feathersjs/feathers-chat/
Additionally, I'd like to define an event listener that should store the messages it receives to the app's messages service and call all necessary hooks to notify the client application.
Here's my current approach:
module.exports = function () {
const app = this;
const Model = createModel(app);
const paginate = app.get('paginate');
const options = {
name: 'messages',
Model,
paginate
};
app.use('/messages', createService(options));
const service = app.service('messages');
service.hooks(hooks);
const sender = new MyExternalMessageSender();
sender.on('message', (msg) => {
service.create(msg, {user: {_id: 0}}).then(result => console.log(result));
});
if (service.filter) {
service.filter(filters);
}
};
This sometimes works fine and sometimes it randomly results in an error as soon as MyExternalMessageSender is notified and tries to call the message service's create method.
NotAuthenticated: No auth token
at Error.NotAuthenticated (projects\feathers-chat\node_modules\feathers-errors\lib\index.js:100:17)
at projects\feathers-chat\node_modules\feathers-authentication\lib\hooks\authenticate.js:102:31
How can I store messages the correct way without my application itself needing to use a JWT?
Thanks for your support!
I am not sure what MyExternalMessageSender does but authentication is skipped by default in internal service calls. If it is an internal service call is determined by params.provider being set. So if you pass hook.params from an external call (where provider is normally set to rest or socketio) to subsequent service calls authentication will run (since it thinks it is an external call).
This can be avoided by removing the provider property before passing the original parameters e.g. with Lodash _.omit:
myservice.find(_.omit(params, 'provider'))
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
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.
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
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();
});