New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

socketio-auth

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

socketio-auth - npm Package Compare versions

Comparing version 0.0.2 to 0.0.3

.editorconfig

96

lib/socketio-auth.js

@@ -0,56 +1,52 @@

'use strict';
var _ = require('underscore');
var _ = require('lodash');
var debug = require('debug')('socketio-auth');
function forbidConnections(nsp) {
/*
Set a listener so connections from unauthenticated sockets are not
considered when emitting to the namespace. The connections will be
restored after authentication succeeds.
*/
nsp.on('connect', function(socket){
if (!socket.auth) {
debug('removing socket from %s', nsp.name);
delete nsp.connected[socket.id];
}
});
}
function restoreConnection(nsp, socket) {
/*
If the socket attempted a connection before authentication, restore it.
*/
if (_.findWhere(nsp.sockets, {id: socket.id})) {
debug('restoring socket to %s', nsp.name);
nsp.connected[socket.id] = socket;
}
}
module.exports = function(io, config){
/*
Adds connection listeners to the given socket.io server, so clients
are forced to authenticate before they can receive events.
*/
/**
* Adds connection listeners to the given socket.io server, so clients
* are forced to authenticate before they can receive events.
*
* @param {Object} io - the socket.io server socket
*
* @param {Object} config - configuration values
* @param {Function} config.authenticate - indicates if authentication was successfull
* @param {Function} config.postAuthenticate=noop - called after the client is authenticated
* @param {Number} [config.timeout=1000] - amount of millisenconds to wait for a client to
* authenticate before disconnecting it
*/
module.exports = function socketIOAuth(io, config) {
config = config || {};
var timeout = config.timeout || 1000;
var postAuthenticate = config.postAuthenticate || function(){};
var postAuthenticate = config.postAuthenticate || _.noop;
_.each(io.nsps, forbidConnections);
io.on('connection', function(socket){
io.on('connection', function(socket) {
socket.auth = false;
socket.on('authentication', function(data){
socket.on('authentication', function(data) {
config.authenticate(data, function(err, success){
config.authenticate(data, function(err, success) {
if (success) {
debug('Authenticated socket %s', socket.id);
socket.auth = true;
_.each(io.nsps, function(nsp) {
restoreConnection(nsp, socket);
});
socket.emit('authenticated', success);
return postAuthenticate(socket, data);
} else if (err) {
debug('Authentication error socket %s: %s', socket.id, err.message);
socket.emit('unauthorized', {message: err.message}, function() {
socket.disconnect();
});
} else {
debug('Authentication failure socket %s', socket.id);
socket.emit('unauthorized', {message: 'Authentication failure'}, function() {
socket.disconnect();
});
}
socket.disconnect('unauthorized', {err: err});
});

@@ -60,4 +56,4 @@

setTimeout(function(){
//If the socket didn't authenticate after connection, disconnect it
setTimeout(function() {
// If the socket didn't authenticate after connection, disconnect it
if (!socket.auth) {

@@ -71,1 +67,25 @@ debug('Disconnecting socket %s', socket.id);

};
/**
* Set a listener so connections from unauthenticated sockets are not
* considered when emitting to the namespace. The connections will be
* restored after authentication succeeds.
*/
function forbidConnections(nsp) {
nsp.on('connect', function(socket) {
if (!socket.auth) {
debug('removing socket from %s', nsp.name);
delete nsp.connected[socket.id];
}
});
}
/**
* If the socket attempted a connection before authentication, restore it.
*/
function restoreConnection(nsp, socket) {
if (_.findWhere(nsp.sockets, {id: socket.id})) {
debug('restoring socket to %s', nsp.name);
nsp.connected[socket.id] = socket;
}
}
{
"name": "socketio-auth",
"version": "0.0.2",
"version": "0.0.3",
"description": "Authentication for socket.io",

@@ -10,3 +10,7 @@ "main": "index.js",

"scripts": {
"test": "node_modules/mocha/bin/mocha"
"jscs": "jscs lib/ test/",
"jshint": "jshint lib/ test/",
"lint": "npm run jshint && npm run jscs",
"pretest": "npm run lint",
"test": "mocha"
},

@@ -32,7 +36,9 @@ "repository": {

"debug": "^2.1.3",
"underscore": "^1.7.0"
"lodash": "^3.8.0"
},
"devDependencies": {
"jscs": "~1.8.0",
"jshint": "~2.5.10",
"mocha": "^1.21.5"
}
}

@@ -1,9 +0,40 @@

# socketio-auth
# socketio-auth [![Build Status](https://secure.travis-ci.org/invisiblejs/socketio-auth.png)](http://travis-ci.org/invisiblejs/socketio-auth)
This module provides hooks to implement authentication in [socket.io](https://github.com/Automattic/socket.io) without using querystrings to send credentials, which is not a good security practice.
It works by marking the clients as unauthenticated by default and listening to an `authentication` event. If a client provides wrong credentials or doesn't authenticate it gets disconnected. While the server waits for a connected client to authenticate, it won't emit any events to it.
Client:
```javascript
var socket = io.connect('http://localhost');
socket.on('connect', function(){
socket.emit('authentication', {username: "John", password: "secret"});
socket.on('authenticated', function() {
// use the socket as usual
});
});
```
## Usage
Server:
```javascript
var io = require('socket.io').listen(app);
require('socketio-auth')(io, {
authenticate: function (data, callback) {
//get credentials sent by the client
var username = data.username;
var password = data.password;
db.findUser('User', {username:username}, function(err, user) {
//inform the callback of auth success/failure
if (err || !user) return callback(new Error("User not found"));
return callback(null, user.password == password);
}
}
});
```
The client should send an `authentication` event right after connecting, including whatever credentials are needed by the server to identify the user (i.e. user/password, auth token, etc.). The `authenticate` function receives those same credentials and uses them to authenticate.
## Configuration
To setup authentication for the socket.io connections, just pass the server socket to socketio-auth with a configuration object:

@@ -50,10 +81,36 @@

The client just needs to make sure to authenticate after connecting:
## Auth error messages
When client authentication fails, the server will emit an `unauthorized` event with the failure reason:
```javascript
var socket = io.connect('http://localhost');
socket.on('connect', function(){
socket.emit('authentication', {username: "John", password: "secret"});
socket.emit('authentication', {username: "John", password: "secret"});
socket.on('unauthorized', function(err){
console.log("There was an error with the authentication:", err.message);
});
```
The server will emit the `authenticated` event to confirm authentication.
The value of `err.message` depends on the outcome of the `authenticate` function used in the server: if the callback receives an error its message is used, if the success parameter is false the message is `'Authentication failure'`
```javascript
function authenticate(data, callback) {
db.findUser('User', {username:data.username}, function(err, user) {
if (err || !user) {
//err.message will be "User not found"
return callback(new Error("User not found"));
}
//if wrong password err.message will be "Authentication failure"
return callback(null, user.password == data.password);
}
}
```
After receiving the `unauthorized` event, the client is disconnected.
## Implementation details
**socketio-auth** implements two-step authentication: upon connection, the server marks the clients as unauthenticated and listens to an `authentication` event. If a client provides wrong credentials or doesn't authenticate after a timeout period it gets disconnected. While the server waits for a connected client to authenticate, it won't emit any broadcast/namespace events to it. By using this approach the sensitive authentication data, such as user credentials or tokens, travel in the body of a secure request, rather than a querystring that can be logged or cached.
Note that during the window while the server waits for authentication, direct messages emitted to the socket (i.e. `socket.emit(msg)`) *will* be received by the client. To avoid those types of messages reaching unauthorized clients, the emission code should either be defined after the `authenticated` event is triggered by the server or the `socket.auth` flag should be checked to make sure the socket is authenticated.
See [this blog post](https://facundoolano.wordpress.com/2014/10/11/better-authentication-for-socket-io-no-query-strings/) for more details on this authentication method.

@@ -0,1 +1,3 @@

'use strict';
var assert = require('assert');

@@ -6,91 +8,105 @@ var EventEmitter = require('events').EventEmitter;

function NamespaceMock(name) {
this.name = name;
this.sockets = [];
this.connected = {}
this.name = name;
this.sockets = [];
this.connected = {};
}
util.inherits(NamespaceMock, EventEmitter);
NamespaceMock.prototype.connect = function(client) {
this.sockets.push(client);
this.connected[client.id] = client;
this.emit('connection', client);
}
this.sockets.push(client);
this.connected[client.id] = client;
this.emit('connection', client);
};
function ServerSocketMock () {
this.nsps = {
"/User": new NamespaceMock("/User"),
"/Message": new NamespaceMock("/Message")
this.nsps = {
'/User': new NamespaceMock('/User'),
'/Message': new NamespaceMock('/Message')
};
}
util.inherits(ServerSocketMock, EventEmitter);
ServerSocketMock.prototype.connect = function(nsp, client) {
this.emit('connection', client);
this.nsps[nsp].connect(client);
}
this.emit('connection', client);
this.nsps[nsp].connect(client);
};
ServerSocketMock.prototype.emit = function(event, data, cb) {
ServerSocketMock.super_.prototype.emit.call(this, event, data);
//fakes client acknowledgment
if (cb) {
process.nextTick(cb);
}
};
function ClientSocketMock(id) {
this.id = id;
this.client = {}
this.id = id;
this.client = {};
}
util.inherits(ClientSocketMock, EventEmitter);
ClientSocketMock.prototype.disconnect = function() {
this.emit('disconnect');
}
this.emit('disconnect');
};
function authenticate(data, cb) {
if(!data.token) return cb(new Error("Missing credentials"));
cb(null, data.token == "fixedtoken");
if (!data.token) {
cb(new Error('Missing credentials'));
}
cb(null, data.token === 'fixedtoken');
}
describe('Server socket authentication', function(){
var server;
var client;
describe('Server socket authentication', function() {
var server;
var client;
beforeEach(function(){
beforeEach(function() {
server = new ServerSocketMock();
require('../lib/socketio-auth')(server, {
timeout:80,
authenticate: authenticate
});
});
client = new ClientSocketMock(5);
});
});
it('Should mark the socket as unauthenticated upon connection', function(done) {
assert(client.auth == undefined);
server.connect("/User", client);
process.nextTick(function(){
assert(client.auth == false);
it('Should mark the socket as unauthenticated upon connection', function(done) {
assert(client.auth === undefined);
server.connect('/User', client);
process.nextTick(function() {
assert(client.auth === false);
done();
});
});
});
it('Should not send messages to unauthenticated sockets', function(done) {
server.connect("/User", client);
process.nextTick(function(){
server.connect('/User', client);
process.nextTick(function() {
assert(!server.nsps['/User'][5]);
done();
});
});
});
it('Should disconnect sockets that do not authenticate', function(done) {
server.connect("/User", client);
client.on('disconnect', function(){
done();
server.connect('/User', client);
client.on('disconnect', function() {
done();
});
});
});
it('Should authenticate with valid credentials', function(done) {
server.connect("/User", client);
process.nextTick(function(){
client.on('authenticated', function(){
server.connect('/User', client);
process.nextTick(function() {
client.on('authenticated', function() {
assert(client.auth);
done();
});
client.emit('authentication', {token: "fixedtoken"});
client.emit('authentication', {token: 'fixedtoken'});
});
});
});

@@ -102,7 +118,7 @@ it('Should call post auth function', function(done) {

var postAuth = function(socket, tokenData) {
assert.equal(tokenData.token, "fixedtoken");
assert.equal(tokenData.token, 'fixedtoken');
assert.equal(socket, client);
done();
}
};
require('../lib/socketio-auth')(server, {

@@ -114,39 +130,74 @@ timeout:80,

server.connect("/User", client);
process.nextTick(function(){
client.emit('authentication', {token: "fixedtoken"});
server.connect('/User', client);
process.nextTick(function() {
client.emit('authentication', {token: 'fixedtoken'});
});
});
});
it('Should send updates to authenticated sockets', function(done) {
server.connect("/User", client);
process.nextTick(function(){
client.on('authenticated', function(){
server.connect('/User', client);
process.nextTick(function() {
client.on('authenticated', function() {
assert.equal(server.nsps['/User'].connected[5], client);
done();
});
client.emit('authentication', {token: "fixedtoken"});
client.emit('authentication', {token: 'fixedtoken'});
});
});
it('Should not authenticate without credentials', function(done) {
server.connect("/User", client);
process.nextTick(function(){
client.once('disconnect', function(){
it('Should send error event on invalid credentials', function(done) {
server.connect('/User', client);
process.nextTick(function() {
client.once('unauthorized', function(err) {
assert.equal(err.message, 'Authentication failure');
done();
});
client.emit('authentication', {});
client.emit('authentication', {token: 'invalid'});
});
});
it('Should not authenticate with invalid credentials', function(done) {
server.connect("/User", client);
process.nextTick(function(){
client.once('disconnect', function(){
it('Should send error event on missing credentials', function(done) {
server.connect('/User', client);
process.nextTick(function() {
client.once('unauthorized', function(err) {
assert.equal(err.message, 'Missing credentials');
done();
});
client.emit('authentication', {token: "invalid"});
client.emit('authentication', {});
});
});
it('Should disconnect on missing credentials', function(done) {
server.connect('/User', client);
process.nextTick(function() {
client.once('unauthorized', function() {
//make sure disconnect comes after unauthorized
client.once('disconnect', function() {
done();
});
});
client.emit('authentication', {});
});
});
it('Should disconnect on invalid credentials', function(done) {
server.connect('/User', client);
process.nextTick(function() {
client.once('unauthorized', function() {
//make sure disconnect comes after unauthorized
client.once('disconnect', function() {
done();
});
});
client.emit('authentication', {token: 'invalid'});
});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc