So I have an application running node js with socket.io as a backend and normal javascript as frontend. My application has a login system which currently simply has the client send its login data as soon as it's connected.
Now I figured it would be much nicer to have the login data sent along with the handshakeData, so I can directly have the user logged in while connecting (instead of after establishing a connection) respectively refuse authorization when the login data is invalid.
I'm thinking it would be best to put my additional data in the header part of the handshakeData, so any ideas how I could do that? (Without having to modify socket.io if possible, but if it's the only way I can live with it)
As a lot of comments have pointed out below the Socket.IO API changed in their 1.0 release. Authentication should now be done via a middleware function, see 'Authentication differences' # http://socket.io/docs/migrating-from-0-9/#authentication-differences. I'll include my orginal answer for anyone stuck on <1.0 as the old docs seem to be gone.
1.0 and later:
Client Side:
//The query member of the options object is passed to the server on connection and parsed as a CGI style Querystring.
var socket = io("http://127.0.0.1:3000/", { query: "foo=bar" });
Server Side:
io.use(function(socket, next){
console.log("Query: ", socket.handshake.query);
// return the result of next() to accept the connection.
if (socket.handshake.query.foo == "bar") {
return next();
}
// call next() with an Error if you need to reject the connection.
next(new Error('Authentication error'));
});
Pre 1.0
You can pass a query: param in the second argument to connect() on the client side which will be available on the server in the authorization method.
I've just been testing it. On the client I have:
var c = io.connect('http://127.0.0.1:3000/', { query: "foo=bar" });
On the server:
io.set('authorization', function (handshakeData, cb) {
console.log('Auth: ', handshakeData.query);
cb(null, true);
});
The output on the server then looked like:
:!node node_app/main.js
info - socket.io started
Auth: { foo: 'bar', t: '1355859917678' }
Update
3.x and later
You can pass an authentication payload using the auth param as the second argument to connect() in the client side.
Client Side:
io.connect("http://127.0.0.1:3000/", {
auth: {
token: "AuthToken",
},
}),
In server side you can access it using socket.handshake.auth.token
Server Side:
io.use(function(socket, next){
console.log(socket.handshake.auth.token)
next()
});
This has now been changed in v1.0.0. See the migration docs
basically,
io.set('authorization', function (handshakeData, callback) {
// make sure the handshake data looks good
callback(null, true); // error first, 'authorized' boolean second
});
becomes :
io.use(function(socket, next) {
var handshakeData = socket.request;
// make sure the handshake data looks good as before
// if error do this:
// next(new Error('not authorized');
// else just call next
next();
});
For socket.io v1.2.1 use this:
io.use(function (socket, next) {
var handshake = socket.handshake;
console.log(handshake.query);
next();
});
This my code for sending query data to nodejs and server.io server client.
var socket = io.connect(window.location.origin, { query: 'loggeduser=user1' });
io.sockets.on('connection', function (socket) {
var endp = socket.manager.handshaken[socket.id].address;
console.log("query... " + socket.manager.handshaken[socket.id].query.user);
}
Perhaps the api has changed but I did the following to get extra info to the server.
// client
io.connect('localhost:8080', { query: 'foo=bar', extra: 'extra'});
// server
io.use(function(sock, next) {
var handshakeData = sock.request;
console.log('_query:', handshakeData._query);
console.log('extra:', handshakeData.extra);
next();
});
prints
_query: { foo: 'bar',
EIO: '3',
transport: 'polling',
t: '1424932455409-0' }
extra: undefined
If anyone knows how to get data from a client to the server through the handshake that is not in the query params let me know please.
Update I ran into issues later with this syntax
io.connect('localhost:8080?foo=bar');
is what I'm currently using.
Old thread but assuming you store your jwt token/session id in session cookies (standard stuff) this gets passed to the server by default anyway when doing handshake (socket.io-client) I've noticed.
Is there anything wrong with just getting the auth information for the handshake (via middleware or on.connection) via cookie?
eg.
io.on('connection', function(socket) {
// assuming base64url token
const cookieStr = socket.handshake.headers.cookie
const matchRes =
cookieStr == null
? false
: cookieStr.match(/my-auth-token=([a-zA-Z0-9_.-]+)/)
if (matchRes) {
// verify your jwt...
if ( tokenIsGood(matchRes[1]) {
// handle authenticated new socket
} else {
socket.emit('AUTH_ERR_LOGOUT')
socket.disconnect()
}
} else {
socket.emit('AUTH_ERR_LOGOUT')
socket.disconnect()
}
}
I'm using this now for a project and it's working fine.
I found a little problem to see the .loggeduser
io.sockets.on('connection', function (socket) {
var endp = socket.manager.handshaken[socket.id].address;
console.log("query... " + socket.manager.handshaken[socket.id].query.loggeduser);
// ↑ here
}
Related
I declare socket server in separate module. I have access to the object of the server everywhere in application, for example I can emmit. But I can't add listener in the route, for example:
router.post('/example', function(req, res, next) {
var socketio = req.app.get('sock');
socketio.sockets.on('connection', function (socket) {
socket.on('message', function (text) {
console.log('ok');
});
});
Is there any way to do this?
If there is just a single device that you want to communicate with and it should have already connected to your server and req.app.get('sock') is how you get access to the socketio server object, then you can do it like this:
var theDeviceSocket;
req.app.get('sock').on('connection', function(socket) {
theDeviceSocket = socket;
});
router.post('/example', function(req, res, next) {
if (theDeviceSocket) {
theDeviceSocket.emit("someMsg", "someData");
}
// send whatever response you want to send
res.end();
});
If you were trying to get a response from the single device and return that as the response to the POST, then you could so something like this:
// store the one connection to/from the special device
var theDeviceSocket;
// keep a request cntr so we can tell which response goes with which request
var requestCntr = 0;
req.app.get('sock').on('connection', function(socket) {
theDeviceSocket = socket;
});
router.post('/example', function(req, res, next) {
var timer, thisRequestId;
function gotData(data) {
// if this is our specific response
if (data.requestId === thisRequestId) {
theDeviceSocket.removeListener("someMsgResponse", gotData);
res.send(data);
clearTimeout(timer);
}
}
if (theDeviceSocket) {
theDeviceSocket.on("someMsgResponse", gotData);
thisRequestId = requestCntr++;
theDeviceSocket.emit("someMsg", {requestId: thisRequestId});
// set up a timeout in case we don't get the proper response
timer = setTimeout(function() {
theDeviceSocket.removeListener("someMsgResponse", gotData);
res.send("error");
}, 5000);
}
});
This second scheme is complicated by the fact that you have architected this to be a request/response scheme, but socket.io is not a request/response protocol. So, in order to know which response belongs to which request (when there are potentially multiple clients interacting with the server at the same time), you have to implement some sort of requestId in the data you send and receive over socket.io. This means you have to change the device to echo back the requestId you sent it with its response. All of this would not be necessary if you used a protocol that is designed for request/response like HTTP instead of socket.io.
I'm using node js, express and postgresql as backend.
This is the approach I used to make a rest API:
exports.schema = function (inputs, res) {
var query = knex('schema')
.orderBy('sch_title', 'asc')
.select();
query.exec(function (err, schemas) {
if(err){
var response = {
message: 'Something went wrong when trying to fetch schemas',
thrownErr: err
};
console.error(response);
res.send(500, response);
}
if(schemas.length === 0){
var message = 'No schemas was found';
console.error(message);
res.send(400, message);
return;
}
res.send(200, schemas);
});
};
It works but after a while postgres logs an error and it's no longer working:
sorry, too man clients already
Do I need a close each request somehow? Could not find any about this in the express docs. What can be wrong?
This error only occurs on production server. Not on developing machine.
Update
The app only brakes in one 'module'. The rest of the app works fine. So it's only some queries that gives the error.
Just keep one connection open for your whole app. The docs shows an example how to do this.
This code goes in your app.js...
var Knex = require('knex');
Knex.knex = Knex.initialize({
client: 'pg',
connection: {
// your connection config
}
});
And when you want to query in your controllers/middlewares...
var knex = require('knex').knex;
exports.schema = function (req, res) {
var query = knex('schema')
.orderBy('sch_title', 'asc')
.select();
// more code...
};
If you place Knex.initialize inside an app.use or app.VERB, it gets called repeatedly for each request thus you'll end up connecting to PG multiple times.
For most cases, you don't need to do an open+query+close for every HTTP request.
I am trying to hold session data across my whole web-app, but I have difficulties to get the same sessionID everywhere.
What's the use of the sessionID inside of this snippet of code (used with express):
app.get('/test-page', function(req, res) {
console.log(req.sessionID);
});
Every time I open the page I get a new sessionID here.
Furthermore, I am using this code for getting the sessionID from the cookie in my socket.io:
io.set('authorization', function(handshakeData, accept) {
if (handshakeData.headers.cookie) {
handshakeData.cookie = parseCookie(handshakeData.headers.cookie);
handshakeData.sessionID = handshakeData.cookie['express.sid'];
} else {
handshakeData.sessionID = -1;
}
accept(null, true);
});
io.sockets.on('connection', function(socket) {
console.log('client connected, ssID: ' + socket.handshake.sessionID);
});
How to get this same session in app.get('/test-page',... ? Or vice versa.
I never got to figuring this out but you could always do some rewriting to express.io
Checkout the sessions example, seems to be exactly what you need.
I make the first steps in the node js and xmpp
I need to run at xmpp server on node js for messaging
Here's the process:
I use node-xmpp server https://github.com/astro/node-xmpp
run the example of a server (/examples/c2s.js)
join to server with two clients (clients tested on other servers jabber - it works and the messages are sending through)
Clients have authorization on my server.
But when I send a message from one client to another, the message comes to the server (I see it in the logs)
and that was the message does not come to recipient
I don `t know where to look for the problem
server configuration ? routing ? messaging may be necessary to add yourself ?
help me plz
my server code (by examples)
var xmpp = require('../lib/node-xmpp');
var c2s = new xmpp.C2SServer({
port: 5222,
domain: 'localhost'
});
// On Connect event. When a client connects.
c2s.on("connect", function(client) {
c2s.on("register", function(opts, cb) {
console.log("REGISTER");
cb(true);
});
client.on("authenticate", function(opts, cb) {
console.log("AUTH" + opts.jid + " -> " +opts.password);
cb(null);
});
client.on("online", function() {
console.log("ONLINE");
client.send(new xmpp.Message({ type: 'chat' }).c('body').t("Hello there, little client."));
});
client.on("stanza", function(stanza) {
console.log("STANZA" + stanza);
});
client.on("disconnect", function(client) {
console.log("DISCONNECT");
});
});
I run a server and connect to it by this code
var xmpp = require('../lib/node-xmpp');
var argv = process.argv;
if (argv.length < 6) {
console.error('Usage: node send_message.js <my-jid> <my-password> <my-text> <jid1> [jid2] ... [jidN]');
process.exit(1);
}
var cl = new xmpp.Client({ jid: argv[2], password: argv[3] });
cl.addListener('online',
function() {argv.slice(5).forEach(
function(to) {cl.send(new xmpp.Element('message', { to: to,type: 'chat'}).c('body').t(argv[4]));
});
// nodejs has nothing left to do and will exit
// cl.end();
});
cl.addListener('stanza',
function(stanza) {
if (stanza.is('message') &&
// Important: never reply to errors!
stanza.attrs.type !== 'error') {
console.log("New message");
// Swap addresses...
stanza.attrs.to = stanza.attrs.from;
delete stanza.attrs.from;
// and send back.
cl.send(stanza);
}
});
cl.addListener('error',
function(e) {
console.error(e);
process.exit(1);
});
The short answer: change cb(null) to cb(null, opts).
The long answer:
client.on("authenticate", function(opts, cb) {...}) registers what the server will do when the client tries to authenticate itself. Inside node-xmpp, it will look for the authentication mechanism first and the mechanism will then call the callback and retrieve the authentication results via cb.
By default, the Plain authentication is used. You can check out how it works here: https://github.com/node-xmpp/node-xmpp-server/blob/master/lib/authentication/plain.js. With Plain the opts stores the jid and password.
Then to inform node-xmpp that authentication failed/sucessed, we need to look into mechanism, https://github.com/node-xmpp/node-xmpp-server/blob/master/lib/authentication/mechanism.js, inherited by Plain.
this.authenticate(this.extractSasl(auth), function (err, user) {
if (!err && user) {
self.success(user)
} else {
self.failure(err)
}
})
Here, cb requires two parameters. When err is null and user is non-null, it indicates authentication successes. Otherwise, failed.
I am no expert on neither node.js nor xmpp. But reading your code. I assume the "stanza" is the event where a client sent a message. You asked it to log the message, but you gave no instructions on how to route it to the recipient. You should break down the received message on the server into message body and recipient, and ask your server to send it to the recipient.
Alex you have used C2SServer which connects a stream between a server and a client. When you send a message from one client to another they get to server. Now its responsibility of the sever to relay them back to actual receiver.
One possible solution is to keep client object is a global object corresponding to their jids when client is authenticated, when you get a message for that client you can get that from global variable and route the message to actual client kept in global variable.
You can get the text message & receiver JID from server. Just break the stanza in following ways and put this before error listeners:-
cl.on('stanza', function(stanza) {
if (stanza.is('message') && (stanza.attrs.type !== 'error')) {
var body = stanza.getChild('body');
if (!body) {
return;
}
console.log(stanza.attrs.from+" Says: "+body.children[0]);
}
});
In "authenticate", may an argument not be enough for a callback?
NG:
cb(null);
OK:
cb(null, opts);
So I have an application running node js with socket.io as a backend and normal javascript as frontend. My application has a login system which currently simply has the client send its login data as soon as it's connected.
Now I figured it would be much nicer to have the login data sent along with the handshakeData, so I can directly have the user logged in while connecting (instead of after establishing a connection) respectively refuse authorization when the login data is invalid.
I'm thinking it would be best to put my additional data in the header part of the handshakeData, so any ideas how I could do that? (Without having to modify socket.io if possible, but if it's the only way I can live with it)
As a lot of comments have pointed out below the Socket.IO API changed in their 1.0 release. Authentication should now be done via a middleware function, see 'Authentication differences' # http://socket.io/docs/migrating-from-0-9/#authentication-differences. I'll include my orginal answer for anyone stuck on <1.0 as the old docs seem to be gone.
1.0 and later:
Client Side:
//The query member of the options object is passed to the server on connection and parsed as a CGI style Querystring.
var socket = io("http://127.0.0.1:3000/", { query: "foo=bar" });
Server Side:
io.use(function(socket, next){
console.log("Query: ", socket.handshake.query);
// return the result of next() to accept the connection.
if (socket.handshake.query.foo == "bar") {
return next();
}
// call next() with an Error if you need to reject the connection.
next(new Error('Authentication error'));
});
Pre 1.0
You can pass a query: param in the second argument to connect() on the client side which will be available on the server in the authorization method.
I've just been testing it. On the client I have:
var c = io.connect('http://127.0.0.1:3000/', { query: "foo=bar" });
On the server:
io.set('authorization', function (handshakeData, cb) {
console.log('Auth: ', handshakeData.query);
cb(null, true);
});
The output on the server then looked like:
:!node node_app/main.js
info - socket.io started
Auth: { foo: 'bar', t: '1355859917678' }
Update
3.x and later
You can pass an authentication payload using the auth param as the second argument to connect() in the client side.
Client Side:
io.connect("http://127.0.0.1:3000/", {
auth: {
token: "AuthToken",
},
}),
In server side you can access it using socket.handshake.auth.token
Server Side:
io.use(function(socket, next){
console.log(socket.handshake.auth.token)
next()
});
This has now been changed in v1.0.0. See the migration docs
basically,
io.set('authorization', function (handshakeData, callback) {
// make sure the handshake data looks good
callback(null, true); // error first, 'authorized' boolean second
});
becomes :
io.use(function(socket, next) {
var handshakeData = socket.request;
// make sure the handshake data looks good as before
// if error do this:
// next(new Error('not authorized');
// else just call next
next();
});
For socket.io v1.2.1 use this:
io.use(function (socket, next) {
var handshake = socket.handshake;
console.log(handshake.query);
next();
});
This my code for sending query data to nodejs and server.io server client.
var socket = io.connect(window.location.origin, { query: 'loggeduser=user1' });
io.sockets.on('connection', function (socket) {
var endp = socket.manager.handshaken[socket.id].address;
console.log("query... " + socket.manager.handshaken[socket.id].query.user);
}
Perhaps the api has changed but I did the following to get extra info to the server.
// client
io.connect('localhost:8080', { query: 'foo=bar', extra: 'extra'});
// server
io.use(function(sock, next) {
var handshakeData = sock.request;
console.log('_query:', handshakeData._query);
console.log('extra:', handshakeData.extra);
next();
});
prints
_query: { foo: 'bar',
EIO: '3',
transport: 'polling',
t: '1424932455409-0' }
extra: undefined
If anyone knows how to get data from a client to the server through the handshake that is not in the query params let me know please.
Update I ran into issues later with this syntax
io.connect('localhost:8080?foo=bar');
is what I'm currently using.
Old thread but assuming you store your jwt token/session id in session cookies (standard stuff) this gets passed to the server by default anyway when doing handshake (socket.io-client) I've noticed.
Is there anything wrong with just getting the auth information for the handshake (via middleware or on.connection) via cookie?
eg.
io.on('connection', function(socket) {
// assuming base64url token
const cookieStr = socket.handshake.headers.cookie
const matchRes =
cookieStr == null
? false
: cookieStr.match(/my-auth-token=([a-zA-Z0-9_.-]+)/)
if (matchRes) {
// verify your jwt...
if ( tokenIsGood(matchRes[1]) {
// handle authenticated new socket
} else {
socket.emit('AUTH_ERR_LOGOUT')
socket.disconnect()
}
} else {
socket.emit('AUTH_ERR_LOGOUT')
socket.disconnect()
}
}
I'm using this now for a project and it's working fine.
I found a little problem to see the .loggeduser
io.sockets.on('connection', function (socket) {
var endp = socket.manager.handshaken[socket.id].address;
console.log("query... " + socket.manager.handshaken[socket.id].query.loggeduser);
// ↑ here
}