On my website users need to login with database account. My question is, how to auth socket.io only if database login pass and create my variable in cookie ?
Express with auth function from here socket.io Auth works with sessionID, maybe i can prevent to give sessionID to moment when user log in to database ?
Use the authorization function:
io.set('authorization', function (handshakeData, callback) {
// findDatabyip is an async example function
findDatabyIP(handshakeData.address.address, function (err, data) {
if (err) return callback(err);
if (data.authorized) {
handshakeData.foo = 'bar';
for(var prop in data) handshakeData[prop] = data[prop];
callback(null, true);
} else {
callback(null, false);
}
})
});
Replace findDatabyIP with your database lookup.
Any attributes you set on handshakeData should be available in your "connection" handler via socket.handshake.sessionID.
Call callback when you're done, with an error message (if applicable), and true if the connection is authorized, false if it's not.
Parsing and reading the session's cookie may be difficult. This discussion (especially jugglinmike's post) may help.
Related
I am building an integration with Express Session and I am trying to authenticate the user in a separate webhook. So I need a way to update the user session outside of the request since it would be a different request.
What I did is I passed the session ID to the new request, and use MongoClient to update the session in the database. I remember Express Session only stores the ID on the client-side and the data is store on the database, so I assume updating the database would do it. But that doesn't seem to be the case. I can clearly see that the session on MongoDB is all updated but I kept getting the outdated data in req.session.data.
Here's what I've done
So the first thing I tried is to use the req.session.reload() method like these:
Updating express-session sessions
change and refresh session data in Node.js and express-session
But it is still giving me outdated data as if the function did nothing (it did print out logs so I assume it did run).
I tried using this that uses store.get() method from Express Session but it is giving me undefined session.
Express load Session from Mongo with session_id
So as a last resort I use MongoClient to get the session data directly and update the session with the data obtained. I use async for the route handler and await the MongoClient to get the data. It doesn't wait for the await and just kept throwing undefined. I thought it's my code that's wrong but I am able to get user data with the code and it did wait for the MongoClient to get the data, but somehow it is not working for session data.
Here's part of my code:
app.router.get('/login', async function (req, res, next) {
req.session.auth = await mongo.getCookie(req.session.id).auth;
req.session.user = await mongo.getCookie(req.session.id).user;
console.log('Login Action', req.session.id, req.session.auth, req.session.user);
}
module.exports.getCookie = async function getCookie(identifier) {
try {
let database = client.db('database');
let collection = await database.collection('sessions');
let result = await collection.findOne({ _id: identifier }, function(err, res) {
if (err) throw err;
return res;
});
return result;
}
catch (err) {
console.error(err);
return null;
}
}
Here's other answers that I've check
This one only update the expiration so I assume its not going to work for me, I did set the resave to false so that it doesn't try to save every single request, since its saving to the database I assume it has nothing to do with updating and I have tried setting it to true and nothing happened.
Renewing/Refreshing Express Session
And in this it uses socket.io and store.get() so it's not going to work as well.
How to access session in express, outside of the req?
Can someone guide me on how do I get Express Session to update from the database or is there a way to get the data from the database and update the session that way?
So I did eventually figured it out, turns out the session was not updated from the database. Looks like it has a local copy in the cache, but I was under the impression that the express session only uses the database.
I know for a fact that my data is indeed in the database but not in the cache or wherever the local copy of the express session is stored. So the easiest way to get pass this problem is to update the local copy with the data on the database.
And I created this function to update the session data
async function sessionUpdate(req) {
try {
// get the data on the database
let tempSession = await mongo.getCookie(req.session.id);
// if the data exist, we can copy that to our current session
if (tempSession) {
req.session.auth = tempSession.auth;
req.session.user = tempSession.user;
// you can do other stuff that you need to do with the data too
if (req.session.health == null || req.session.health == undefined || typeof req.session.health === 'undefined') {
req.session.health = 0;
}
// This update and save the data to the session
req.session.save( function(err) {
req.session.reload( function (err) {
//console.log('Session Updated');
});
});
}
else if (!tempSession) {
req.session.auth = false;
req.session.user = null;
req.session.ip = request.connection.remoteAddress;
}
}
catch (err) {
console.error(err);
}
}
Now that we have a function that can update the session data, we need to implement it everywhere. I use middleware to do that, you can use other methods too but this must be called before the route or the session logic.
// middleware with no mounted path so it is used for all requests
receiver.router.use(async function (req, res, next) {
try {
await sessionUpdate(req);
}
catch (err) {
console.error(err);
}
next();
});
It's not exactly a good/efficient way of doing it... that's a lot of things to run before even getting to the route so I would assume it would bring the performance down a bit... But this is how I get it to work and if anyone can come up with some better idea I would very much appreciate it 🙌
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
}
I am using SailsJS and Socket.IO.
Currently I need to know if a user's session is expired or not.
Once user's session is expired I need to disconnect socket connection for that user.
One way to do it is using CRON for every 30 minutes and checking if user is still logged in.
Is there any way I can check if user's session still exists without using CRON.
Thanks in Advance
As luck would have it, Node is Javascript and Javascript makes this sort of thing very easy. You can just use setInterval like in a regular ol' front-end script. So, assuming you have a User model with an expiresOn field you can check do something like the following in your config/bootstrap.js:
module.exports = function(cb) {
// Schedule disconnectExpiredUsers to run once a minute
setInterval(disconnectExpiredUsers, 60000);
// Function to loop through all expired users and disconnect them
function disconnectExpiredUsers() {
var now = new Date();
User.find({expiresOn: {'<': now}}).exec(function(err, expiredUsers) {
// Handle errors somehow
if (err) {
return console.log(err);
}
// Loop through expired users (lodash is globalized by Sails)
_.each(expiredUsers, function(expiredUser) {
// Since you're using resourceful pubsub (right???),
// we can get all of the instance's sockets easily
var sockets = User.subscribers(expiredUser);
// And we can send them a message
User.publishMessage(expiredUser, "Session expired, buddy!");
// And disconnect them, but use nextTick to make sure the messages
// go out first
process.nextTick(function() {
_.each(sockets, function(socket) {socket.disconnect();});
});
});
});
}
};
This also assumes you're using Sails' resourceful pubsub to subscribe sockets to your User instances; otherwise you'll have to keep track of the IDs of connected sockets yourself.
You can do this by adding policy to all route
Simply add sessionAuth.js to policy folder
module.exports = function(req, res, next) {
// If you are not using passport then set your own logic
/*if (req.session.authenticated) {
return next();
}*/
// if you are using passport then
if(req.isAuthenticated()) {
return next();
}
//else
// Write your code for socket disconnect
// or send json res like this
return res.json({msg:'You are not loggedin, Please Login !'})
};
Now go to config/policies.js
And apply this policy to all routes
module.exports.policies = {
'*': 'sessionAuth'
}
I am trying to add functionality to my error handler by not only logging the message to the console, but by redirecting the client's browser to a static HTML page that would display some simple text content. Here is the existing handler:
var sql = require('msnodesql');
//store a connection to MS SQL Server-----------------------------------------------------------------------------------
sql.open(connStr, function(err, sqlconn){
if(err){
console.error("Could not connect to sql: ", err);
}
else
conn = sqlconn; //save the sql connection globally for all client's to use
});
I'm using express.js to create my web server. This is server side code. I want this to happen in realtime, as soon as the error occurs the client's web browser gets redirected.
EDIT: I guess what I really want to know is how to redirect the client's browser to a page from inside if(err).
You can use a middleware that checks the state of the connection for each request and renders an appropriate template depending on the state (instead of using res.render, you can also use res.redirect or res.sendfile, of course):
var sql = require('msnodesql');
var conn = null;
sql.open(connStr, function(err, sqlconn) {
if (err) {
console.error("Could not connect to sql: ", err);
conn = false;
} else {
conn = sqlconn;
}
});
// Express middleware that checks the connection state of the database
// connection: active, not yet active, or failed.
app.use(function(req, res, next) {
// database connection not active yet
if (conn === null || conn === undefined) {
res.status(503);
return res.render('not-active-yet');
}
// database connection failed
if (conn === false) {
res.status(500);
return res.render('db-connection-failed');
}
// everything seems okay
next();
});
EDIT: forgot to mention that you need to include this middleware very early in the middleware chain, but certainly before any of your routes.
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
}