I have a php login, the user puts in a username/password, it checks the mysql db against the login information. If authenticated a session is created via php and the user can now access the system with the php session. My question is once they authenticate via php/session what would be the process to authorize the user to see if they have the right login permissions to access a nodejs server with socket.io? I dont want the person to have access to the nodejs/socket.io function/server unless they have authenticated via the php login.
4 Answers
Update
Requirements:
- First have redis running.
- Next fire up socket.io.
- Finally upload/host PHP(has dependencies in archive).
Socket.io
var express = require('express'),
app = express.createServer(),
sio = require('socket.io'),
redis = require("redis"),
client = redis.createClient(),
io = null;
/**
* Used to parse cookie
*/
function parse_cookies(_cookies) {
var cookies = {};
_cookies && _cookies.split(';').forEach(function( cookie ) {
var parts = cookie.split('=');
cookies[ parts[ 0 ].trim() ] = ( parts[ 1 ] || '' ).trim();
});
return cookies;
}
app.listen(3000, "localhost");
io = sio.listen(app);
io.of('/private').authorization(function (handshakeData, callback) {
var cookies = parse_cookies(handshakeData.headers.cookie);
client.get(cookies.PHPSESSID, function (err, reply) {
handshakeData.identity = reply;
callback(false, reply !== null);
});
}).on('connection' , function (socket) {
socket.emit('identity', socket.handshake.identity);
});
PHP
php with openid authentication => http://dl.dropbox.com/u/314941/6503745/php.tar.gz
After login you have to reload client.php
to authenticate
p.s: I really don't like the concept of creating even another password which is probably is going to be unsafe. I would advice you to have a look at openID(via Google for example), Facebook Connect(just name a few options).
My question is once they authenticate via php/session what would be the process to authenticate the user to see if they have the right login permissions to access a nodejs server with socket.io? I dont want the person to have access to the nodejs/socket.io function/server unless they have authenticated via the php login.
Add the unique session_id to a list/set of allowed ids so that socket.io can authorize(search for authorization function) that connection. I would let PHP communicate with node.js using redis because that is going to be lightning fast/AWESOME :). Right now I am faking the PHP communication from redis-cli
Install Redis
Download redis => Right now the stable version can be downloaded from: http://redis.googlecode.com/files/redis-2.2.11.tar.gz
alfred@alfred-laptop:~$ mkdir ~/6502031
alfred@alfred-laptop:~/6502031$ cd ~/6502031/
alfred@alfred-laptop:~/6502031$ tar xfz redis-2.2.11.tar.gz
alfred@alfred-laptop:~/6502031$ cd redis-2.2.11/src
alfred@alfred-laptop:~/6502031/redis-2.2.11/src$ make # wait couple of seconds
Start Redis-server
alfred@alfred-laptop:~/6502031/redis-2.2.11/src$ ./redis-server
Socket.io
npm dependencies
If npm
is not already installed , then first visit http://npmjs.org
npm install express
npm install socket.io
npm install redis
listing the dependencies I have installed and which you should also probably install in case of incompatibility according to npm ls
alfred@alfred-laptop:~/node/socketio-demo$ npm ls
/home/alfred/node/socketio-demo
├─┬ express@2.3.12
│ ├── connect@1.5.1
│ ├── mime@1.2.2
│ └── qs@0.1.0
├── hiredis@0.1.12
├── redis@0.6.0
└─┬ socket.io@0.7.2
├── policyfile@0.0.3
└── socket.io-client@0.7.2
Code
server.js
var express = require('express'),
app = express.createServer(),
sio = require('socket.io'),
redis = require("redis"),
client = redis.createClient(),
io = null;
/**
* Used to parse cookie
*/
function parse_cookies(_cookies) {
var cookies = {};
_cookies && _cookies.split(';').forEach(function( cookie ) {
var parts = cookie.split('=');
cookies[ parts[ 0 ].trim() ] = ( parts[ 1 ] || '' ).trim();
});
return cookies;
}
app.listen(3000, "localhost");
io = sio.listen(app);
io.configure(function () {
function auth (data, fn) {
var cookies = parse_cookies(data.headers.cookie);
console.log('PHPSESSID: ' + cookies.PHPSESSID);
client.sismember('sid', cookies.PHPSESSID, function (err , reply) {
fn(null, reply);
});
};
io.set('authorization', auth);
});
io.sockets.on('connection', function (socket) {
socket.emit('access', 'granted');
});
To run server just run node server.js
client.php
<?php
session_start();
echo "<h1>SID: " . session_id() . "</h1>";
?>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script src="http://localhost:3000/socket.io/socket.io.js"></script>
</head>
<body>
<p id="text">access denied</p>
<script>
var socket = io.connect('http://localhost:3000/');
socket.on('access', function (data) {
$("#text").html(data);
});
</script>
</body>
Test authentication
When you load the webpage(PHP-file) from your web-browser the message access denied
is shown, but when you add the session_id
also shown in browser to redis server the message access granted
will be shown. Of course normally you would not be doing any copy pasting but just let PHP communicate with Redis directly.. But for this demo you will put SID
ramom807vt1io3sqvmc8m4via1
into redis after which access has been granted.
alfred@alfred-laptop:~/database/redis-2.2.0-rc4/src$ ./redis-cli
redis> sadd sid ramom807vt1io3sqvmc8m4via1
(integer) 1
redis>
-
2Wouldn't be this approach vulnerable to man-in-the-middle attack or impersonation - let's say that client will change his cookie SID value to some other SID (which will be also valid in the current context) and therefore he will be impersonated as the other user? – yojimbo87 Jun 28 '11 at 11:17
-
@Alfred Thank you for the explanation. So are you saying pretty much on every page I should be updating this record for the user? Because otherwise this would fail after awhile I would assume due to the use of regenerating the id via php. Also should the name sid be a unique identifier per user? Or is the unique identifier the sid key itself? – John Jun 28 '11 at 13:59
-
I also saw they have php classes/interfaces to store session data in redis. Sounds like this would be faster than using the normal way of storing sessions in a mysql db, do you agree Alfred? – John Jun 28 '11 at 19:00
-
1@yojimbo87. This will be vulnerable to man-in-the-middle attack. You can't do a thing about that in PHP(alone). All your PHP is also probably vulnerable to that. You can/should minimize the exposure by calling `session_regenerate_id`. But after that the only thing you can do to protect yourself against man-in-the-middle attack is using SSL. – Alfred Jun 28 '11 at 20:32
-
@John during your session the session_id remains the same. The session_id is how your cookie can keep track of your session => http://pastebin.com/FJkC7xmt. We compare the cookie inside node.js to authorize the session. About using redis to store your session is also something I would consider ;). Like you said it can help you scale better. Last but not least I would like to update my sample a little bit to really do authentication(via google openid) and do the authorization part also in PHP instead of faking it via `redis-cli`. – Alfred Jun 28 '11 at 20:42
-
@Alfred: I see, and what about the case where some user rewrites his cookie SID to the SID of another connected user and therefore he will be impersonated? Will the HttpOnly cookie flag and encryption help in this case? – yojimbo87 Jun 28 '11 at 20:44
-
@Alfred Wait Im confused. You said the session id will stay the same even after calling session_regenerate_id? I thought that changed the session_id – John Jun 28 '11 at 20:52
-
@John You are right that the session_id changes when you call session_regenerate_id and you should use that to prevent session fixation, but after that call it will stay the same during the session. I hope I made myself clear. – Alfred Jun 28 '11 at 22:02
-
@Yojimbo87 encryption does not work if the cookie is stolen. It Helps against reading the information inside the cookie, but not using the cookie. The http flag does help against cookie stealing when your site has a XSS vulnerability(but only if user is using a good browser, because not all browsers support http flag). – Alfred Jun 28 '11 at 22:31
-
@Alfred Just need help to clear this up, when I run the session_renegerate_id function it changes the session id, if I do that then do I need to update redis with the new sid? Thats my hangup right now that I just need to verify. – John Jun 28 '11 at 23:26
-
@John that's true :). But you should update redis only after session_generate_id – Alfred Jun 28 '11 at 23:59
-
@Alfred Ok thats all I wanted to verify. I understand the code and believe me you have been a HUGE help with understanding all of this. I know I have a long way to go but you have given me a great starting point. Thanks again! – John Jun 29 '11 at 00:01
-
@John no problem I like helping people :). It also helps me better giving people help, which I think is a got skill... If you have any tips for me I also appreciate that. Also I hope to improve the code to be also utilizing redis at the PHP side in a little bit. Stay tuned(hopefully 1 hour). – Alfred Jun 29 '11 at 00:10
-
Oops I need a little more time to improve code because I wandered a little bit. But first I need to sleep a little bit... – Alfred Jun 29 '11 at 04:06
-
@Alfred: So even when the cookie content is encrypted, it can be stolen, attached to other "malicious" session and the server side will still decrypt it as valid (resulting in successful authentication)? – yojimbo87 Jun 29 '11 at 07:30
-
@Alfred Quick question, when adding the sid to redis does the key need to be unique per login? Your example redis-cli command adds a key called sid with the value of the session id. But if I use sid for all logins it would overwrite the last value wouldnt it? – John Jun 29 '11 at 11:53
-
@John those SIDs are unique for every session. That is just the way that PHP uniquely identifies each session(in place). I recommend you to also read http://shiflett.org/articles/storing-sessions-in-a-database to better understand that concept. I am going to work on a better implementation in a little while(using redis from PHP)... – Alfred Jun 29 '11 at 12:30
-
@Alfred I get that part, but if I went into redis-cli and typed in sadd sid ramom807vt1io3sqvmc8m4via1 then typed in sadd sid dsf5453dfgdsgdf5453 I was thinking the second command would override the first command. Are you saying running both commands will create 2 seperate entries in redis? – John Jun 29 '11 at 12:39
-
@yojimbo87 PHP's session uses cookies under the cover to uniquely identify every session. When PHP has the session it gets the rest of the data serverside(from filesystem/database) so you won't have to encrypt it anyway. PHP session only store PHPSID inside the cookie but if you steal that information you are in. Some sites encrypt the cookie to not be human-readable(which it is). But when I use the exact same stolen cookie from your machine on my machine. I could use that data to authenticate or something because your server will decrypt it and use it on the server. I hope it makes any sense – Alfred Jun 29 '11 at 12:40
-
@John SADD uses a SET under the cover and adds a new element to the set. While SISMEMBER check if that id is member of that Set. To be honest I would probably be better of storing sessions in redis immediately. Then I can directly use it from both ends when I have the sid(which I can read from socket.io ;)). I will try to implement that as well in my new implementation which hopefully I can share in about one hour(optimistic ;)). Because I wanted to share a prototype quickly on SO I did some hacking... – Alfred Jun 29 '11 at 12:43
-
@John If you like we could chat via XMPP at alfredwesterveld@jabber.org – Alfred Jun 29 '11 at 12:47
-
Lol It stores data `serialize`d so I can't access it from node.js until PHP's serialize is available in node.js. – Alfred Jun 29 '11 at 13:09
-
@Alfred: Yes it makes sense, thanks for explanation. I was interested namely in case of stealing cookies and authenticating from another computer by malicious user. Does it mean that if I want to achieve secure, impersonation-free connection with certain client then I would have to use https? – yojimbo87 Jun 29 '11 at 13:56
-
@Alfred Ahh ok. Should of read up on the commands more. This url is helping a lot: http://redistogo.com/documentation/introduction_to_redis – John Jun 29 '11 at 14:53
-
2@Alfred, you should really join the socket.io google group and help them out with redis, apparently a redis memorystore will be integrated in 0.8! Also, nice one with all the redis help, I see you participating in a lot of answers! Kudos! – PaulM Jun 30 '11 at 21:54
-
@PaulM it is already integrated. I am not using redis to communicate(PHPSID) between PHP and node.js in a fast/easy fashion. – Alfred Jul 07 '11 at 13:46
-
-
Hello @Alfred i know it's been a while since your last answer/comment but my question is: my php application is in mybackendapp.dev and the nodejs socket.io run at http://localhost:1921/socket.io/socket.io.js how can i deal with that. – Marcel Djaman Feb 20 '14 at 09:20
-
@Alfred, I'm looking for a way to access the PHPSESSID in socket.io using express 4. There just doesn't seem to be any example code out there at the moment. Could you amend your example to work with more recent modules? – Asa Carter May 28 '14 at 23:30
-
-
@think123 I don't think i have that repo anymore :$. But I would not use this anymore, but use pusher which is free for normal use and very scalable. – Alfred May 08 '16 at 12:36
-
@Alfred Oh :/ is there any way that I can get a secure connection over Socket.IO? Or do I have to use a priced solution like Pusher? – Lucas May 09 '16 at 00:00
Remember that sessions are just files stored in the php sessions directory. It won't be a problem for node.js to get the session id from the cookie and then check if the session really exists in the sessions directory. To get the path of the sessions directory refer to the session.save_path directive in your php.ini.

- 1,796
- 18
- 26
Here's the unserialize and utf8 code if you want it too, originally derived from phpjs.org - had to edit it a bit to make it work with node.js so fish around and compare if you want
function utf8_decode (str_data) {
// http://kevin.vanzonneveld.net
// + original by: Webtoolkit.info (http://www.webtoolkit.info/)
// + input by: Aman Gupta
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: Norman "zEh" Fuchs
// + bugfixed by: hitwork
// + bugfixed by: Onno Marsman
// + input by: Brett Zamir (http://brett-zamir.me)
// + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// * example 1: utf8_decode('Kevin van Zonneveld');
// * returns 1: 'Kevin van Zonneveld'
var tmp_arr = [],
i = 0,
ac = 0,
c1 = 0,
c2 = 0,
c3 = 0;
str_data += '';
while (i < str_data.length) {
c1 = str_data.charCodeAt(i);
if (c1 < 128) {
tmp_arr[ac++] = String.fromCharCode(c1);
i++;
} else if (c1 > 191 && c1 < 224) {
c2 = str_data.charCodeAt(i + 1);
tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
i += 2;
} else {
c2 = str_data.charCodeAt(i + 1);
c3 = str_data.charCodeAt(i + 2);
tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return tmp_arr.join('');
}
exports.utf8_decode = utf8_decode;
function unserialize (data) {
// http://kevin.vanzonneveld.net
// + original by: Arpad Ray (mailto:arpad@php.net)
// + improved by: Pedro Tainha (http://www.pedrotainha.com)
// + bugfixed by: dptr1988
// + revised by: d3x
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + input by: Brett Zamir (http://brett-zamir.me)
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: Chris
// + improved by: James
// + input by: Martin (http://www.erlenwiese.de/)
// + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: Le Torbi
// + input by: kilops
// + bugfixed by: Brett Zamir (http://brett-zamir.me)
// - depends on: utf8_decode
// % note: We feel the main purpose of this function should be to ease the transport of data between php & js
// % note: Aiming for PHP-compatibility, we have to translate objects to arrays
// * example 1: unserialize('a:3:{i:0;s:5:"Kevin";i:1;s:3:"van";i:2;s:9:"Zonneveld";}');
// * returns 1: ['Kevin', 'van', 'Zonneveld']
// * example 2: unserialize('a:3:{s:9:"firstName";s:5:"Kevin";s:7:"midName";s:3:"van";s:7:"surName";s:9:"Zonneveld";}');
// * returns 2: {firstName: 'Kevin', midName: 'van', surName: 'Zonneveld'}
var that = this;
var utf8Overhead = function (chr) {
// http://phpjs.org/functions/unserialize:571#comment_95906
var code = chr.charCodeAt(0);
if (code < 0x0080) {
return 0;
}
if (code < 0x0800) {
return 1;
}
return 2;
};
var error = function (type, msg, filename, line) {
console.log('[[[[[[[[[[[[[[[[[[ERROR]]]]]]]]]]]]]]]]]]]','msg:', msg, 'filename:',filename, 'line:',line);
};
var read_until = function (data, offset, stopchr) {
if (stopchr == ';' && !data.match(/;$/)) data += ';';
var buf = [];
var chr = data.slice(offset, offset + 1);
var i = 2;
while (chr != stopchr) {
if ((i + offset) > data.length) {
error('Error', 'Invalid','php.js','126');
}
buf.push(chr);
chr = data.slice(offset + (i - 1), offset + i);
i += 1;
//console.log('i:',i,'offset:',offset, 'data:',data,'chr:',chr,'stopchr:',stopchr);
}
return [buf.length, buf.join('')];
};
var read_chrs = function (data, offset, length) {
var buf;
buf = [];
for (var i = 0; i < length; i++) {
var chr = data.slice(offset + (i - 1), offset + i);
buf.push(chr);
length -= utf8Overhead(chr);
}
return [buf.length, buf.join('')];
};
var _unserialize = function (data, offset) {
var readdata;
var readData;
var chrs = 0;
var ccount;
var stringlength;
var keyandchrs;
var keys;
if (!offset) {
offset = 0;
}
var dtype = (data.slice(offset, offset + 1)).toLowerCase();
var dataoffset = offset + 2;
var typeconvert = function (x) {
return x;
};
switch (dtype) {
case 'i':
typeconvert = function (x) {
return parseInt(x, 10);
};
readData = read_until(data, dataoffset, ';');
chrs = readData[0];
readdata = readData[1];
dataoffset += chrs + 1;
break;
case 'b':
typeconvert = function (x) {
return parseInt(x, 10) !== 0;
};
readData = read_until(data, dataoffset, ';');
chrs = readData[0];
readdata = readData[1];
dataoffset += chrs + 1;
break;
case 'd':
typeconvert = function (x) {
return parseFloat(x);
};
readData = read_until(data, dataoffset, ';');
chrs = readData[0];
readdata = readData[1];
dataoffset += chrs + 1;
break;
case 'n':
readdata = null;
break;
case 's':
ccount = read_until(data, dataoffset, ':');
chrs = ccount[0];
stringlength = ccount[1];
dataoffset += chrs + 2;
readData = read_chrs(data, dataoffset + 1, parseInt(stringlength, 10));
chrs = readData[0];
readdata = readData[1];
dataoffset += chrs + 2;
if (chrs != parseInt(stringlength, 10) && chrs != readdata.length) {
error('SyntaxError', 'String length mismatch','php.js','206');
}
// Length was calculated on an utf-8 encoded string
// so wait with decoding
readdata = utf8_decode(readdata);
break;
case 'a':
readdata = {};
keyandchrs = read_until(data, dataoffset, ':');
chrs = keyandchrs[0];
keys = keyandchrs[1];
dataoffset += chrs + 2;
for (var i = 0; i < parseInt(keys, 10); i++) {
var kprops = _unserialize(data, dataoffset);
var kchrs = kprops[1];
var key = kprops[2];
dataoffset += kchrs;
var vprops = _unserialize(data, dataoffset);
var vchrs = vprops[1];
var value = vprops[2];
dataoffset += vchrs;
readdata[key] = value;
}
dataoffset += 1;
break;
default:
error('SyntaxError', 'Unknown / Unhandled data type(s): ' + dtype,'php.js','238');
break;
}
return [dtype, dataoffset - offset, typeconvert(readdata)];
};
return _unserialize((data + ''), 0)[2];
}
exports.unserialize = unserialize;

- 5,281
- 2
- 29
- 27
I was looking over the solutions here and decided to give what rcode said a try because it seemed so much easier than the gigantic wall of code accepted answer.
It ended up working nicely and is quite easy to do.
I did end up installing a few dependencies which I wanted to avoid but is relatively easy to do with node.
Type the following in console:
npm install cookie
npm install php-unserialize
This solution uses the session files on the machine - you shouldn't have to change this line.
session.save_handler = files
^ Should be like this in your php.ini file (default).
(People suggested using memcache, but it seemed like a headache to switch over to that system.)
Here is the super simple code to retrieve the session data:
var cookie = require('cookie');
var fs = require('fs');
var phpUnserialize = require('php-unserialize');
//This should point to your php session directory.
//My php.ini says session.save_path = "${US_ROOTF}/tmp"
var SESS_PATH = "C:/SomeDirectory/WhereYourPHPIs/tmp/";
io.on('connection', function(socket) {
//I just check if cookies are a string - may be better method
if(typeof socket.handshake.headers.cookie === "string") {
var sid = cookie.parse(socket.handshake.headers.cookie);
if(typeof sid.PHPSESSID === "undefined") {
console.log("Undefined PHPSESSID");
}
else {
console.log("PHP Session ID: " + sid.PHPSESSID);
fs.readFile(SESS_PATH + "sess_" + sid.PHPSESSID, 'utf-8', function(err,data) {
if(!err) {
console.log("Session Data:");
var sd = phpUnserialize.unserializeSession(data);
console.log(sd);
}
else {
console.log(err);
}
});
}
}
}
Results:
Edit: I just wanted to add that it may be easier to just have PHP tell your Node.js server when someone logs in and pass the credentials along there.
I explain how to do this pretty easily in another answer.

- 7,786
- 3
- 37
- 74