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'
}
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 🙌
I am trying to set up a chat interface on a site using nodejs.
I need to update the logged_in_user list after a user is authenticated.
I am using this code as a reference
https://github.com/amirrajan/nodejs-chat
io.sockets.on('connection', (socket) => {
socket.on('sendchat', (data) => {
io.sockets.emit('updatechat', socket.username, data);
});
As you can see on the 'connection' event the updatechat event is triggered.
In the client side (public/app.js),the chatlist is updated on getting the updatechat event.
I need to do the same but after a client is authenticated.
Can I somehow tweak the socket object to trigger the event on a 'login' event
This is my code
app.get('/authenticate',function(request,response)
{
var db = couch.db.use('users');
var email = request.query['email'];
var pass = request.query['password'];
db.get(email,{email:email},function(err,body){
if(err)
console.log(err);
else
{
var user_pass = body['password'];
var name = body['name'];
console.log(user_pass);
if(pass == user_pass)
{
eventEmitter.emit('login',name);
response.render('pages/dashboard',{user:body['name']});
}
}
});
// And on 'login' event
eventEmitter.on('login', function(name)
{
// ?????? Broadcast the new user to all logged in users to update the chatlist
});
I have tried to redirect the logged in users to another port and use the socket 'connection' event there.It sort of works but I want to know if there is another approach I could use.
You should use socket.io middleware for authentication.
io.use(function(socket,next){
//auth here
});
Take a look at the docs http://socket.io/docs/server-api/
You can get the req and res objects from the socket object being passed to the middleware.
Note, it is not a great idea to pass a password on the query since the url is not pass ssl.
i want to send datas in same client (not all clients) with this code;
app.post("/search", function(req, res) {
//some other codes
//if database saved {
io.sockets.emit('preview-post', {title: "blabla" });// io variable was declared as GLOBAL
// } database saved end.
res.send({bla:bla});// response end before database saving process
});
this sample is working ! but it sends to all clients , How can i emit data to same opened browser(same client) ?
Second Question is: Are there any alternative ways to do this scenario?
My algorithm is post method fired > async call to an api > response end and page loaded on client > async call to an api is still continue > if async call is finished > send alert to client . But How? i wanted to do it wiht socket .io , if i use your 3.part , it'll work , can i do this scenario any other way?
This indeed sends to all sockets.
There are a couple ways to achieve what you are looking to do. The first way is to do something like this on the server:
io.on('connection', function(socket) {
socket.on('search', function(*/ client supplied arguments /*){
socket.emit('preview-post', {title: "blabla" });
});
});
If you are insistent on using a post request, and then sending it back to the client, there are two ways to achieve this.
The easiest way, if you only need to respond to this response, is just send a standard response from node, and let the client handle it:
res.send({
event: 'preview-post',
payload: {title: "blabla" }
});
This removes socket.io's event system, so if you are insistent on using socket.io to send this event back to the same client, you are going to need to use cookies. Express and the module cookie-parser make this easy for you.
Once you have this setup, inside your request you could do something like this:
app.post("/search", function(req, res) {
var socket = findSocketByCookie(req.cookies.myUniqueCookie);
socket.emit('preview-post', {title: "blabla" });
});
function findSocketByCookie(cookie) {
for(var i in io.sockets.connected) {
var socket = io.sockets.connected[i];
if(socket.handshake.headers.cookie.indexOf(cookie) !== -1){
return socket;
}
}
}
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.
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
}