Authenticate user for socket.io/nodejs

32,639

Solution 1

Update

Requirements:

  1. First have redis running.
  2. Next fire up socket.io.
  3. 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
├─┬ [email protected] 
│ ├── [email protected] 
│ ├── [email protected] 
│ └── [email protected] 
├── [email protected] 
├── [email protected] 
└─┬ [email protected] 
  ├── [email protected] 
  └── [email protected] 

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.auth. 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> 

Solution 2

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.

Share:
32,639
John
Author by

John

Updated on April 17, 2020

Comments

  • John
    John about 4 years

    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.

  • yojimbo87
    yojimbo87 almost 13 years
    Wouldn'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?
  • John
    John almost 13 years
    @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
    John almost 13 years
    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?
  • Alfred
    Alfred almost 13 years
    @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
    Alfred almost 13 years
    @John during your session the session_id remains the same. The session_id is how your cookie can keep track of your session => 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.
  • yojimbo87
    yojimbo87 almost 13 years
    @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?
  • John
    John almost 13 years
    @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
  • Alfred
    Alfred almost 13 years
    @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
    Alfred almost 13 years
    @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).
  • John
    John almost 13 years
    @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.
  • Alfred
    Alfred almost 13 years
    @John that's true :). But you should update redis only after session_generate_id
  • John
    John almost 13 years
    @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!
  • Alfred
    Alfred almost 13 years
    @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
    Alfred almost 13 years
    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...
  • yojimbo87
    yojimbo87 almost 13 years
    @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)?
  • John
    John almost 13 years
    @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?
  • Alfred
    Alfred almost 13 years
    @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 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)...
  • John
    John almost 13 years
    @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?
  • Alfred
    Alfred almost 13 years
    @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
    Alfred almost 13 years
    @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
    Alfred almost 13 years
    @John If you like we could chat via XMPP at [email protected]
  • Alfred
    Alfred almost 13 years
    Lol It stores data serialized so I can't access it from node.js until PHP's serialize is available in node.js.
  • yojimbo87
    yojimbo87 almost 13 years
    @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?
  • John
    John almost 13 years
    @Alfred Ahh ok. Should of read up on the commands more. This url is helping a lot: redistogo.com/documentation/introduction_to_redis
  • PaulM
    PaulM almost 13 years
    @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!
  • Alfred
    Alfred almost 13 years
    @PaulM it is already integrated. I am not using redis to communicate(PHPSID) between PHP and node.js in a fast/easy fashion.
  • PaulM
    PaulM almost 13 years
    @Alfred, is it? Mind if I pick your brains on your jabber a/c?
  • Marcel Djaman
    Marcel Djaman over 10 years
    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 localhost:1921/socket.io/socket.io.js how can i deal with that.
  • Asa Carter
    Asa Carter almost 10 years
    @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?
  • Lucas
    Lucas about 8 years
    @Alfred the link isn't working anymore, could you please update?
  • Alfred
    Alfred about 8 years
    @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.
  • Lucas
    Lucas about 8 years
    @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?