Using JWT with socket.io - javascript

When i try to use JWT with socket.io it wont respsonse when i get connection. I dont get any errors, but i dont get any respsonse when doing things on localhost. The localstorage is empty also. Please help, i've tried everything:)
client.js
var socket = io('http://localhost:4000', {query: 'auth_token=THE_JWT_TOKEN'});
socket.on('error', function(err) {
console.log("test")
throw new Error(err);
});
// Connection succeeded
socket.on('success', function(data) {
console.log(data.message);
console.log('user info: ' + data.user);
console.log('logged in: ' + data.user.logged_in)
})
server.js
var io = require("socket.io")(server);
var socketioJwt = require("socketio-jwt");
// set authorization for socket.io
var io = require('socket.io')();
var jwtAuth = require('socketio-jwt-auth');
var jwt = require('jsonwebtoken')
// using middleware
io.use(jwtAuth.authenticate({
secret: 'Your Secret', // required, used to verify the token's signature
algorithm: 'HS256' // optional, default to be HS256
}, function(payload, done) {
// done is a callback, you can use it as follows
User.findOne({id: payload.sub}, function(err, user) {
if (err) {
// return error
return done(err);
}
if (!user) {
// return fail with an error message
return done(null, false, 'user does not exist');
}
// return success with a user info
return done(null, user);
});
}));
io.on('connection', function(socket) {
console.log('Authentication passed!');
// now you can access user info through socket.request.user
// socket.request.user.logged_in will be set to true if the user was authenticated
socket.emit('success', {
message: 'success logged in!',
user: socket.request.user
});

Related

NodeJS responds before function is done

I'm writing an API with NodeJS and Express for a schoolproject and I'm struggling with the following:
The function getAuthUserId decodes the JWT token and gets the Id from user in the mongoDB server.
I call this function in a REST call "/user/authTest". But when I call this, the server responds before the database can return the Id, and the variable UId is undefined. As you can see, the Id is actually found. Any ideas on how i can fix this?
The API call code:
apiRoutes.post('/user/authTestID', function(req, res) {
var UId = getAuthUserId(req, res);
console.log(UId);
if (UId) {
res.sendStatus(200);
}else{
res.sendStatus(400);
}
});
The function:
function getAuthUserId(req, res) {
var user = new User();
var token = user.getToken(req.headers);
if (token) {
var decoded = jwt.decode(token, config.secret);
User.findOne({
name: decoded.name
}, function(err, user) {
if (err) throw err;
if (!user) {
res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
return false
} else {
console.log('Auth for ' + user.name + ' ' + user._id);
return user._id
}
});
} else {
res.status(403).send({success: false, msg: 'No token provided.'});
return '';
}
}
The output of the terminal:
[nodemon] restarting due to changes...
[nodemon] starting `node server.js`
Connected to MongoDB
undefined
::ffff:192.168.0.111 - POST /user/authTestID HTTP/1.1 400 11 - 175.006 ms
Auth for test 58f8954c3602b80552b6f1fb
Thanks in advance!
You need to make it a promise, like this.
API
apiRoutes.post('/user/authTestID', function(req, res) {
getAuthUserId(req, res).then(function (UId) => {
console.log(UId);
if (UId) {
res.sendStatus(200);
}else{
res.sendStatus(400);
}
});
}, function(err) {
console.log(err.msg)
res.status(err.status).send(err.msg);
});
Function
function getAuthUserId(req, res) {
return new Promise(function(resolve, reject){
var user = new User();
var token = user.getToken(req.headers);
if (token) {
var decoded = jwt.decode(token, config.secret);
User.findOne({
name: decoded.name
}, function(err, user) {
if (err) throw err;
if (!user) {
reject({status: 403, msg: 'Authentication failed. User not found.'});
} else {
console.log('Auth for ' + user.name + ' ' + user._id);
resolve(user._id)
}
});
} else {
reject({status: 403, msg: 'No token provided.'});
}
})
}
getAuthUserId get's the value in a CALLBACK !!! . You can't expect it to return values from it. As quick thing you can do something as below.
apiRoutes.post('/user/authTestID', function (req, res) {
var user = new User();
var token = user.getToken(req.headers);
if (token) {
var decoded = jwt.decode(token, config.secret);
User.findOne({
name: decoded.name
}, function (err, user) {
if (err) throw err;
if (!user) {
return res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
} else {
console.log('Auth for ' + user.name + ' ' + user._id);
return res.send(user._id)
}
});
} else {
return res.status(403).send({success: false, msg: 'No token provided.'});
// return '';
}
});
Try using Promise library like Bluebird
James' comment looks like a good, thorough resource on async calls. As others have mentioned, you cannot return values within a callback. You can use a Promise library, or you can change your getAuthUserId function to take a callback and have your console.log logic in there:
Example:
API call code:
apiRoutes.post('/user/authTestID', function(req, res) {
getAuthUserId(req, res, function(UId) {
// we're in a your new callback
console.log(UId);
if (UId) {
res.sendStatus(200);
}else{
res.sendStatus(400);
}
});
});
Function Code
// note new callback param
function getAuthUserId(req, res, callback) {
var user = new User();
var token = user.getToken(req.headers);
if (token) {
var decoded = jwt.decode(token, config.secret);
User.findOne({
name: decoded.name
}, function(err, user) {
if (err) throw err;
if (!user) {
res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
callback(false) // no more return, call callback with value
} else {
console.log('Auth for ' + user.name + ' ' + user._id);
callback(user._id) // no more return, call callback with value
}
});
} else {
res.status(403).send({success: false, msg: 'No token provided.'});
callback(''); // no more return, call callback with value
}
}

ldapjs - LDAP connection error handling

I am using ldapjs in two sections of my project. The first is using the passport-windowsauth strategy for passportjs and the second is part of my authentication function where I connect to retrieve user roles. I have attached an event handler to the latter to catch error events, but I have never had it trigger. I suspect the error is occurring on the passport strategy, but I am unsure how to attach an event handler to the strategy. How would I go about catching the error event on the strategy? Console log shows the following after about 15 minutes and repeats every couple of minutes indefinitely. Other than the errors in the log, the implementation works perfect.
LDAP connection error: { [Error: read ECONNRESET] code: 'ECONNRESET', errno: 'ECONNRESET', syscall: 'read' }
ldap.js
var ldap = require('ldapjs'),
fs = require('fs');
ldapClient = null;
exports.createClient = function() {
if(ldapClient) return ldapClient;
else {
ldapClient = ldap.createClient({
url: 'ldaps://srv01.contoso.local:636',
reconnect: true,
tlsOptions: {
rejectUnauthorized: true,
ca: [ fs.readFileSync(__dirname + '/../config/ca.pem') ]
}
});
ldapClient.bind('binduser','C0nn3ctM3', function(err) {if(err) {
console.log('Bind error: ' + err); assert.ifError(err); }
});
ldapClient.on('error', function(err) { console.log('Caught ', err.code) })
return ldapClient;
}
}
exports.destroyClient = function() {
if(!ldapClient) return true;
else {
ldapClient.unbind();
ldapClient.destroy();
return true;
}
}
passport.js
var passport = require('passport'),
mongoose = require('mongoose'),
WindowsStrategy = require('passport-windowsauth'),
User = mongoose.model('User'),
fs = require('fs');
module.exports = function() {
passport.use(new WindowsStrategy({
ldap: {
url: 'ldaps://srv01.contoso.local:636/',
base: 'DC=Contoso,DC=local',
bindDN: 'binduser',
bindCredentials: 'C0nn3ctM3',
reconnect: true,
tlsOptions: {
rejectUnauthorized: true,
ca: [ fs.readFileSync(__dirname + '/ca.pem') ]
}
},
integrated: false
}, function(profile, done) {
if(!profile) { console.log('Error: Cannot retrieve profile. Bad password?'); }
else {
User.findOne({'userName': profile._json.sAMAccountName}, function(err, user) {
if(user) {
done(null, user);
}
else {
var newuser = new User();
newuser.getRoles(profile._json.sAMAccountName).then(function(userRoles) {
var account = {
userName: profile._json.sAMAccountName,
firstName: profile._json.givenName,
lastName: profile._json.sn,
emailAddress: profile._json.mail,
roles: userRoles
};
User.create(account, function (err, user) {
if(err) {
if(err.toString().indexOf('E11000') > -1) {
err = new Error('Duplicate username');
}
}
done(null, user);
});
});
}
});
}
}));
passport.serializeUser(function(user, done) {
if(user) {
done(null, user.userName);
}
});
passport.deserializeUser(function(id, done) {
User.findOne({'userName': id}).exec(function(err, user) {
if(user) {
return done(null, user);
}
else {
return done(null, false);
}
})
});
}
Update
I ended up finding the line responsible for the repeated console spamming in the passport-windowsauth file LdapLookup.js and modified it. I would still like to now how I would attach an event handler to a strategy or what the alternative solution would be.
Maybe its too late to reply but for other reader's sake, I'm adding this reply.
ECONNRESET error occurs when after some idle time, the connection to LDAP service is reset. This is a common problem since a prolonged connection to ldap cannot be maintained. I too faced this problem recently.
There are multiple ways to solve this problem.
Set reconnect option true. I see that you've already done that b
Next option would be to destroy the client when ERRCONNRESET occur and recreate. You've already implemented the destroy client and recreating it every time.
You could also find this link which discuss this problem is detail.
To further add to the response #prajnavantha gave, for the createClient {reconnect: true} option to function, you also have to bind an error event listener to the client or the reconnect will get short circuited by the default error event handler.
For instance:
const ldapjs = require("ldapjs");
const client = ldapjs.createClient({url: 'ldap://host', reconnect: true});
client.on('error', err => {
console.debug('connection failed, retrying');
});
client.bind('user', 'password', err => {
if (err) process.exit();
});
// ...

ReactJS + MongoDB + NodeJS/ExpressJS: What is process.nextTick(function() { throw err; });?

In my ReactJS project, I am currently running the server with NodeJS and ExpressJS, and connecting to the MongoDB using MongoClient. I have a login API endpoint set up that accepts a request with user's username and password. And if a user is not found, should catch the error and respond with an error (status(500)) to the front-end.
But rather than responding to the front-end with an json error, the server gets crashed. I have tried everything to figure out why but still no luck.
How can I fix the following error? Any guidance or insight would be greatly appreciated, and will upvote and accept the answer.
I intentionally made a request with a username and a password ({ username: 'iopsert', password: 'vser'}) that does not exist in the database.
Here is the login endpoint:
//login endpoint
app.post('/api/login/', function(req, res) {
console.log('Req body in login ', req.body)
console.log('THIS IS WHAT WAS PASSED IN+++++', req._id)
db.collection('users').findOne({username: req.body.username}, function(err, user) {
console.log('User found ')
if(err) {
console.log('THIS IS ERROR RESPONSE')
// Would like to send this json as an error response to the front-end
res.status(500).send({
error: 'This is error response',
success: false,
})
}
if(user.password === req.body.password) {
console.log('Username and password are correct')
res.status(500).send({
username: req.body.username,
success: true,
user: user,
})
} else {
res.status(500).send({
error: 'Credentials are wrong',
success: false,
})
}
})
And here is the terminal error log:
Req body in login { username: 'iopsert', password: 'vset' }
THIS IS WHAT WAS PASSED IN+++++ undefined
User found
/Users/John/practice-project/node_modules/mongodb/lib/utils.js:98
process.nextTick(function() { throw err; });
^
TypeError: Cannot read property 'password' of null
at /Users/John/practice-project/server/server.js:58:12
at handleCallback (/Users/John/practice-project/node_modules/mongodb/lib/utils.js:96:12)
at /Users/John/practice-project/node_modules/mongodb/lib/collection.js:1395:5
at handleCallback (/Users/John/practice-project/node_modules/mongodb/lib/utils.js:96:12)
at /Users/John/practice-project/node_modules/mongodb/lib/cursor.js:675:5
at handleCallback (/Users/John/practice-project/node_modules/mongodb-core/lib/cursor.js:165:5)
at setCursorNotified (/Users/John/practice-project/node_modules/mongodb-core/lib/cursor.js:505:3)
at /Users/John/practice-project/node_modules/mongodb-core/lib/cursor.js:578:16
at queryCallback (/Users/John/practice-project/node_modules/mongodb-core/lib/cursor.js:226:18)
at /Users/John/practice-project/node_modules/mongodb-core/lib/connection/pool.js:430:18
And /Users/John/practice-project/node_modules/mongodb/lib/utils.js:98 is referring to the following:
var handleCallback = function(callback, err, value1, value2) {
try {
if(callback == null) return;
if(value2) return callback(err, value1, value2);
return callback(err, value1);
} catch(err) {
process.nextTick(function() { throw err; });
return false;
}
return true;
}
EDIT
Here are everything that's being imported to the server:
"use strict"
var express = require('express');
var path = require('path');
var config = require('../webpack.config.js');
var webpack = require('webpack');
var webpackDevMiddleware = require('webpack-dev-middleware');
var webpackHotMiddleware = require('webpack-hot-middleware');
var bodyParser = require('body-parser');
var MongoClient = require('mongodb').MongoClient;
var ObjectId = require('mongodb').ObjectID;
const jwt = require('jsonwebtoken')
var app = express();
var db;
var compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, {noInfo: true, publicPath: config.output.publicPath}));
app.use(webpackHotMiddleware(compiler));
app.use(express.static('dist'));
app.use(bodyParser.json());
And this is how the request is made and error is caught:
loginUser(creds) {
var request = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(creds),
}
fetch(`http://localhost:3000/api/login`, request)
.then(res => res.json())
.then(user => {
console.log(user);
console.log('Successful')
})
.catch(err => {
console.log('Error is', err)
})
},
It looks to me like the error is being thrown on this line because user is not defined.
if(user.password === req.body.password) {...}
Take a harder look at your console statements.
1. Req body in login { username: 'iopsert', password: 'vset' }
2. THIS IS WHAT WAS PASSED IN+++++ undefined
3. User found
4. /Users/John/practice-project/node_modules/mongodb/lib/utils.js:98
5. process.nextTick(function() { throw err; });
^
6. TypeError: Cannot read property 'password' of null
7. at /Users/John/practice-project/server/server.js:58:12
Line 2 shows that req._id is undefined
Your User found statement is printed before you check if there is an error or if the user actually exists, so it isn't representative of there actually being a user.
Line 6 shows that the error is being thrown because you're trying to read a property of password from a null object.
I'd recommend modifying your login logic to look more like this:
//login endpoint
app.post('/api/login/', function(req, res) {
console.log('Performing login with req.body=');
console.log(JSON.stringify(req.body, null, 4));
// check for username
if (!req.body.username) {
return res.status(401).send({message: 'No username'});
}
// find user with username
db.collection('users').findOne({username: req.body.username}, function(err, user) {
// handle error
if(err) {
console.log('Error finding user.');
return res.status(500).send({message: 'Error finding user.'});
}
// check for user
if (!user) {
console.log('No user.');
return res.status(500).send({message: 'No user.'});
}
console.log('User found.');
// check password
if(user.password !== req.body.password) {
console.log('Wrong password.');
return res.status(401).send({message: 'Wrong password.'});
}
// return user info
return res.status(200).send(user);
});
Some final thoughts:
Make sure to handle the error (if it exists) and check that user exists before proceeding.
Always include return in your return res.status(...).send(...) statements, otherwise the subsequent code will execute.
It's generally not a good idea to save passwords as simple strings. Work toward encrypting them. Look at passport or bcrypt.
Hope this helps.

node.js express-session + redis single instance issue

I'm using express-session module to handle my node.js user sessions.
By default it allows multiple sessions per user. I need limit one session per user. I came to the following solution: store user_id:session_id pairs in redis, when user logins check if session for that user_id exists and delete it then create a new one and save it to redis. Everything works excellent until I tried to stress test my server using siege. I emulated simultaneous 1000 login attempts and I see that some sessions are not cleared and still is in redis store.
This allow one user have several sessions. What am I doing wrong?
Please find some code below.
var FileStreamRotator = require('file-stream-rotator'),
app = require('express')(),
fs = require("fs"),
bodyParser = require('body-parser'),
config = require("./providers/config"),
morgan = require('morgan'), //HTTP request logger middleware for node.js
cookieParser = require('cookie-parser'),
redis = require('redis'),
session = require('express-session'),
redisStore = require('connect-redis')(session),
publicRouter = require('./routes/public.js')();
var port = process.env.PORT || config.port;
var client = redis.createClient();
app.disable('x-powered-by');
app.use(cookieParser(config.session.secret));
app.use(session(
{
secret: config.session.secret,
store: new redisStore({host: config.redis.host, port: config.redis.port, client: client}),
saveUninitialized: false, // don't create session until something stored,
resave: false // don't save session if unmodified
}
));
app.use(morgan('combined', {stream: accessLogStream}));
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
*****
app.all('/api/*', [require('./middlewares/validateRequest')]);
******
app.use('/api/public', publicRouter);
******
app.listen(port, function (err) {
if (!err) console.log('Find me on port ' + port + ' and say "Hello"');
else console.log(err);
});
auth.js
var User = require('./../models/user.js');
var Promise = require('bluebird');
var redis = require("./../providers/redis.js");
var util = require('util');
var auth = {
login: function (req, res) {
var login = req.body.login || '';
var password = req.body.password || '';
if (login === '') {
res.status(401);
res.json({
"status": 401,
"message": "login required"
});
return;
}
if (password === '') {
res.status(401);
res.json({
"status": 401,
"message": "password required"
});
return;
}
User.login(login, password)
.then(function (user) {
if (!user) {
res.status(401);
res.json({
"status": 401,
"message": "Incorrect login data."
});
}
return redis.get(util.format("usersess:%s", user.id))
.then(function (currentSession) {
if (currentSession === null) {
redis.set(util.format("usersess:%s", user.id), req.session.id)
.then(function () {
delete user.password;
req.session.user = user;
res.json({
"status": 200,
"message": "User successfully logged in."
});
});
} else {
if (currentSession !== req.session.id) {
return redis.del(util.format("sess:%s", currentSession))
.then(function () {
return redis.set(util.format("usersess:%s", user.id), req.session.id);
})
.then(function () {
delete user.password;
req.session.user = user;
res.json({
"status": 200,
"message": "User successfully logged in."
});
})
} else {
res.json({
"status": 200,
"message": "User successfully logged in."
});
}
}
})
})
.catch(function (err) {
console.log(err);
res.status(500);
res.json({
error: true,
data: {
message: err.message
}
});
});
},
logout: function (req, res) {
req.session.destroy(function (err) {
if (err) {
console.log("Can't destroy the session. See details below");
console.log(err);
res.status(500);
res.json({
"status": 500,
"message": err.message
})
} else {
res.status(200);
res.json({
"status": 200,
"message": "User successfully logged out."
})
}
});
}
};
module.exports = auth;
user model user.js
var Promise = require('bluebird'),
bcrypt = Promise.promisifyAll(require('bcrypt')),
db = require("./../providers/db.js");
var User = {
tableName: 'users',
login: function (login, password) {
if (!login || !password) throw new Error('login and password are both required');
return db.execStoredProcedure("user_get_by_login", [login.trim()])
.then(
function (rows) {
var user = rows[0][0];
return bcrypt.compareAsync(password, user.password)
.then(function (res) {
if (!res) user = null;
return user;
});
}
);
}
};
module.exports = User;
redis provider redis.js
var config = require('./../providers/config');
var Promise = require("bluebird"),
redis = require('promise-redis')(function(resolver) {
return new Promise(resolver);
}),
redisClient = redis.createClient(config.redis.port, config.redis.host),
util = require('util');
redisClient.on('connect', function () {
console.log(util.format('redis connected on %s:%s', config.redis.host, config.redis.port));
});
module.exports = redisClient;
I was unable to find the exact reason why some sessions are not deleted but after a lot of debugging and logs investigating I think it is due to node async nature. While mySQL getting operation require some time, some login actions could run in parallel and get the same values for current user session_id.
To solve this, I created middleware that check if current user session id is in redis store, if it is not--it just destroys the session and logout user asking for a new login attempt. This may be not a good solution but it completely solved the original issue.

Socket.io assigning same id to all clients

I am trying to save all the connected in clients in an object. But as I figured, the connected clients all have the same id's. So if the object connections has all the connected sockets, when i try to send a message to connections['mac'] it just appears on my screen as sent by me to me. Here is the auth code:
app.post('/auth', function(req, res){ // routes.auth(hash, db, io, pseudoArray, connections)
var username = req.body.username,
password = req.body.password;
if (username != "" && password != ""){
authenticate(username, password, db, hash, function(err, user){
if (user) {
// Regenerate session when signing in
// to prevent fixation
console.log('user authenticated');
req.session.regenerate(function(){
req.session.user = user.name;
req.session.success = 'Authenticated as ' + user.name
+ ' click to logout. '
+ ' You may now access /restricted.';
io.sockets.on('connection', function (socket) {
socket.set('pseudo', req.session.user, function(){
pseudoArray.push(req.session.user);
var sessionuser = req.session.user;
socket.emit('pseudoStatus', 'ok');
connections[req.session.user] = socket;
console.log("user " + req.session.user + " connected");
});
});
res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true });
res.redirect('home');
});
} else {
console.log('auth failed');
req.session.error = 'Authentication failed, please check your '
+ ' username and password.'
+ ' (use "tj" and "foobar")';
res.redirect('login');
}
});
} else {
res.redirect('connect');
}
});
The code where I send the message to other client:
exports.addfriend = function(connections, io){
return function(req, res){
// var collection = db.get('go1');
var username = req.session.user;
var friend = req.body.name;
console.log(connections[''+friend+'']);
connections[''+friend+''].emit('addRequest', username);
return;
}
}
I had this auth code in the routes file earlier and I figure I should be running this off app.js. I did that and it still doesnt work as I want it. Can someone tell me what I am missing?
The problem with your code is that you have a Socket.IO incoming connection handler inside one of your HTTP routes. That means the handler is only applied after the route is accessed, besides the fact that you should only have a single Socket.IO connection handler.
What you need to do is separate your HTTP and Socket.IO handlers, and since you're using sessions, allow Socket.IO to handle the authorization.
First, move your socket handler outside of the HTTP handler:
app.post('/', handler);
io.sockets.on('connection', handler);
Then, define the Socket.IO authorization setting to fetch the session object from Express. Assuming you are using express.cookieParser() since you have sessions, and a session store, we need to externally reference them:
var MemoryStore = express.session.MemoryStore;
var session_key = 'express.sid';
var session_secret = 'for signed cookies';
var session_store = new MemoryStore();
var cookieParser = express.cookieParser(session_secret);
app.use(cookieParser);
app.use(express.session({
secret: session_secret,
store: session_store,
key: session_key
});
Now that the session object and cookie parser are accessible, we can define the authorization setting:
io.set('authorization', function(handshake, callback) {
if (handshake.headers.cookie) {
cookieParser(handshake, null, function(err) {
// Use depends on whether you have signed cookies
// handshake.sessionID = handshake.cookies[session_key];
handshake.sessionID = handshake.signedCookies[session_key];
session_store.get(handshake.sessionID, function(err, session) {
if (err || !session) {
callback('Error or no session.', false);
} else {
handshake.session = session;
callback(null, true);
}
});
});
} else {
callback('No session cookie found.', false);
}
});
What this code does is it checks if the client has a session cookie. If not, it does not authorize the Socket.IO connection. If it does, it parses the cookie, finds the associated session, and stores the session with the socket. Now you can access session properties by socket:
app.post('/', function(req, res) {
req.session.user = 'genericusername';
res.send(200);
});
io.sockets.on('connection', function(socket) {
var session = socket.handshake.session;
session.user // genericusername
});
As for your code, it would then look like the following:
var http = require('http');
var express = require('express');
var app = express();
var server = http.createServer(app);
var io = require('socket.io').listen(server);
var MemoryStore = express.session.MemoryStore;
var session_key = 'express.sid';
var session_secret = 'for signed cookies';
var session_store = new MemoryStore();
var cookieParser = express.cookieParser(session_secret);
app.use(cookieParser);
app.use(express.session({
secret: session_secret,
store: session_store,
key: session_key
});
The route handler:
app.post('/auth', function(req, res) {
var username = req.body.username,
var password = req.body.password;
if (username != '' && password != '') {
authenticate(username, password, db, hash, function(err, user) {
if (user) {
req.session.regenerate(function() {
req.session.user = user.name;
req.session.success = 'Authenticated as ' + user.name + ' click to logout. You may now access /restricted.';
res.cookie('rememberme', '1', {
maxAge: 900000,
httpOnly: true
});
res.redirect('/home');
});
} else {
req.session.error = 'Authentication failed, please check your username and password. (use "tj" and "foobar")';
res.redirect('/login');
}
});
} else {
res.redirect('/connect');
}
});
And then the Socket.IO configuration:
io.set('authorization', function(handshake, callback) {
if (handshake.headers.cookie) {
cookieParser(handshake, null, function(err) {
// Use depends on whether you have signed cookies
// handshake.sessionID = handshake.cookies[session_key];
handshake.sessionID = handshake.signedCookies[session_key];
session_store.get(handshake.sessionID, function(err, session) {
if (err || !session) {
callback('Error or no session.', false);
} else {
handshake.session = session;
callback(null, true);
}
});
});
} else {
callback('No session cookie found.', false);
}
});
io.sockets.on('connection', function(socket) {
var session = socket.handshake.sessionl
socket.set('pseudo', session.user, function() {
socket.emit('pseudoStatus', 'ok');
connections[session.user] = socket;
});
});
You have an io.sockets.on('connection'... inside of a single users login. So each time a user logs in, there will be another listener added.
So it may look like this ( someone logs in, then they connect to socket )
User1 logs in
User 1 connects to socket ( pseudo as himself )
User 2 logs in
User 2 connects to socket ( pseudo as User 1, then pseudo as User 2)
User 3 logs in
User 3 connects to socket ( pseudo as 1 , 2, then 3
If user 1 refreshes the page, he will be pseudo as 1, 2, 3, as well
The problem is listening more and more each time someone logs in.
You need to listen to connect outside of a function that happens multiple times.
I usually do something like the following
in english: The user loads up a page, he connects to socket io, then he does a ajax $.get so the server can use his session to give him a token that will authorize his socket. He then sends the token and the server knows that socket is authorized.
app.get('/socket_token', function(req, res, next){
var socket_token = Math.random();/* probably want to use crypto here instead*/
mysql.query('update users set users.socket_token = ? where users.id = ?', [ socket_token, req.session.userId], function(err, result){
if(result)
res.json({userId: req.session.userId, socket_token: socket_token});
else
res.send('DOH!');
});
});
global.all_my_sockets = []
io.sockets.on('connnection', function(socket){
socket.emit('hey_you_are_connected');
socket.on('authorize', function(credentials, callback){
if(credentials.userId)
mysql.query('select * from users where id = ?', [credentials.userId], function(err, user){
if(user.socket_token == credentials.socket_token){
socket.set('authorized', true);
socket.set('userId', user.id);
// This is probably bad since what if he has 2 browser windows open?
all_my_sockets[user.id] = socket
callback('that socket token is authorized');
} else {
//handle it
callback('that socket token is no good');
}
})
});
socket.on('disconnect', function(){
socket.get('userId', function(id){
if(id)
delete all_my_sockets[id];
});
});
})
and on the client side
socket = io.connect();
socket.on('hey_you_are_connected', function(){
$.get('/socket_token', function(credentials){
socket.emit('authorize_token', credentials, function(res){
console.log(res); // did we authorize socket?
});
});
});
I typed this off the top of my head so there might be an error, but it should be pretty close to what you're trying to do.
So there are some problems with saving sockets in all_my_sockets[id], namely that you can only have 1 socket per user and what if they have multiple browsers open?
You should try structuring your code differently so you don't need a global bunch of sockets, otherwise, just push them to the array so multiple can exist for each user.
all_my_sockets.push(socket);
...
socket.on('disconnect', function(){
if( all_my_sockets.indexOf(socket) != -1 )
all_my_sockets.splice( all_my_sockets.indexOf(socket), 1 )
})
if you pushed every socket to the array, before they were authorized, then to actually find the socket then, you would have to go through each:
_.each(all_my_sockets, function(socket){
socket.get('userId', function(userId) {
if(userId)
doSomething(socket)
else
doSomethingUnAuthorized(socket)
});
});
obviously this is very slow, which is the same reason why there aren't a whole lot of people saving sockets to a global place like this. Try doing what you're trying to do with more callbacks and rethink the problem from scratch. Or leave it as a hack until you get more traffic and it becomes a problem of performance. :)

Categories