Authenticate User for Socket.Io/Nodejs

Authenticating socket io connections using JWT

It doesn't matter if the token was created on another server. You can still verify it if you have the right secret key and algorithm.

Implementation with jsonwebtoken module

client

const {token} = sessionStorage;
const socket = io.connect('http://localhost:3000', {
query: {token}
});

Server

const io = require('socket.io')();
const jwt = require('jsonwebtoken');

io.use(function(socket, next){
if (socket.handshake.query && socket.handshake.query.token){
jwt.verify(socket.handshake.query.token, 'SECRET_KEY', function(err, decoded) {
if (err) return next(new Error('Authentication error'));
socket.decoded = decoded;
next();
});
}
else {
next(new Error('Authentication error'));
}
})
.on('connection', function(socket) {
// Connection now authenticated to receive further events

socket.on('message', function(message) {
io.emit('message', message);
});
});

Implementation with socketio-jwt module

This module makes the authentication much easier in both client and server side. Just check out their examples.

client

const {token} = sessionStorage;
const socket = io.connect('http://localhost:3000');
socket.on('connect', function (socket) {
socket
.on('authenticated', function () {
//do other things
})
.emit('authenticate', {token}); //send the jwt
});

Server

const io = require('socket.io')();
const socketioJwt = require('socketio-jwt');

io.sockets
.on('connection', socketioJwt.authorize({
secret: 'SECRET_KEY',
timeout: 15000 // 15 seconds to send the authentication message
})).on('authenticated', function(socket) {
//this socket is authenticated, we are good to handle more events from it.
console.log(`Hello! ${socket.decoded_token.name}`);
});

Authentication in Socket.io

Try using disconnect method from the socket object, something like this:

io.on('connection', function(socket){
//temp delete socket
socket.disconnect();

console.log(io.sockets.connected);
socket.emit("test");
});

UPDATE:

For example if your HTTP server gives to a client a token:

app.post('/api/users', function (req, res) {
var user = {
username: req.body.username
};

var token = jwt.sign(user, secret, {expiresInMinutes: 30});

res.json({token: token});
});

then you can reuse that token to authenticate your websocket connections.

The token sending code from the client (html file) will be:

socket = io.connect('http://localhost:4000', {
query: 'token=' + validToken,
forceNew: true
});

and the socketio authorization code in the server(socketio) will be:

// here is being used a socketio middleware to validate
// the token that has been sent
// and if the token is valid, then the io.on(connection, ..) statement below is executed
// thus the socket is connected to the websocket server.
io.use(require('socketio-jwt').authorize({
secret: secret,
handshake: true
}));

// but if the token is not valid, an error is triggered to the client
// the socket won't be connected to the websocket server.
io.on('connection', function (socket) {
console.log('socket connected');
});

Note that the secret used on the express to generate a token, the same token is being used too on the validation token at socketio middleware.

I have created an example where you can see how this kind of validation works, the source code is here: https://gist.github.com/wilsonbalderrama/a2fa66b4d2b6eca05a5d

copy them in a folder and run the server.js with node and then access the html file from the browser at this URL: http://localhost:4000

but first install the modules: socket.io, express, socketio-jwt, jsonwebtoken

Authenticate user for socket.io/nodejs

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
├─┬ 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.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>

user authentication using socket.io

I know this is bit old, but for future readers in addition to the approach of parsing cookie and retrieving the session from the storage (eg. passport.socketio ) you might also consider a token based approach.

In this example I use JSON Web Tokens which are pretty standard. You have to give to the client page the token, in this example imagine an authentication endpoint that returns JWT:

var jwt = require('jsonwebtoken');
// other requires

app.post('/login', function (req, res) {

// TODO: validate the actual user user
var profile = {
first_name: 'John',
last_name: 'Doe',
email: 'john@doe.com',
id: 123
};

// we are sending the profile in the token
var token = jwt.sign(profile, jwtSecret, { expiresInMinutes: 60*5 });

res.json({token: token});
});

Now, your socket.io server can be configured as follows:

var socketioJwt = require('socketio-jwt');

var sio = socketIo.listen(server);

sio.set('authorization', socketioJwt.authorize({
secret: jwtSecret,
handshake: true
}));

sio.sockets
.on('connection', function (socket) {
console.log(socket.handshake.decoded_token.email, 'has joined');
//socket.on('event');
});

The socket.io-jwt middleware expects the token in a query string, so from the client you only have to attach it when connecting:

var socket = io.connect('', {
query: 'token=' + token
});

I wrote a more detailed explanation about this method and cookies here.



Related Topics



Leave a reply



Submit