diff --git a/README.md b/README.md index ff5b116..fa2da38 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ io.sockets **Client side** ```javascript -var socket = io.connect('http://localhost:9000'); +const socket = io.connect('http://localhost:9000'); socket.on('connect', function () { socket .emit('authenticate', {token: jwt}) //send the jwt @@ -71,8 +71,8 @@ socket.on('connect', function () { The previous approach uses a second roundtrip to send the jwt. There is a way you can authenticate on the handshake by sending the JWT as a query string, the caveat is that intermediary HTTP servers can log the url. ```javascript -var io = require('socket.io')(server); -var socketioJwt = require('socketio-jwt'); +const io = require('socket.io')(server); +const socketioJwt = require('socketio-jwt'); ``` With socket.io < 1.0: @@ -108,7 +108,7 @@ For more validation options see [auth0/jsonwebtoken](https://github.com/auth0/no Append the jwt token using query string: ```javascript -var socket = io.connect('http://localhost:9000', { +const socket = io.connect('http://localhost:9000', { 'query': 'token=' + your_jwt }); ``` @@ -116,7 +116,7 @@ var socket = io.connect('http://localhost:9000', { Append the jwt token using 'Authorization Header' (Bearer Token): ```javascript -var socket = io.connect('http://localhost:9000', { +const socket = io.connect('http://localhost:9000', { 'extraHeaders': { Authorization: `Bearer ${your_jwt}` } }); ``` @@ -144,7 +144,7 @@ io.use(socketioJwt.authorize({ When you sign the token with an expiration time (example: 60 minutes): ```javascript -var token = jwt.sign(user_profile, jwt_secret, {expiresIn: 60*60}); +const token = jwt.sign(user_profile, jwt_secret, {expiresIn: 60*60}); ``` Your client-side code should handle it as below: @@ -234,7 +234,7 @@ the provided token. **Server side** ```javascript -var SECRETS = { +const SECRETS = { 'user1': 'secret 1', 'user2': 'secret 2' } diff --git a/lib/index.js b/lib/index.js index 924d239..5485fcd 100644 --- a/lib/index.js +++ b/lib/index.js @@ -17,12 +17,15 @@ function noQsMethod (options) { } } + let auth_timeout = null; + if (options.required) { + auth_timeout = setTimeout(function () { + socket.disconnect('unauthorized'); + }, options.timeout || 5000); + } + socket.on('authenticate', function (data) { if (options.required) { - let auth_timeout = setTimeout(function () { - socket.disconnect('unauthorized'); - }, options.timeout || 5000); - clearTimeout(auth_timeout); } @@ -30,13 +33,12 @@ function noQsMethod (options) { const onError = function (err, code) { if (err) { code = code || 'unknown'; - const error = new UnauthorizedError(code, { message: (Object.prototype.toString.call(err) === '[object Object]' && err.message) ? err.message : err }); let callback_timeout; - // If callback explicitely set to false, start timeout to disconnect socket + // If callback explicitly set to false, start timeout to disconnect socket if (options.callback === false || typeof options.callback === 'number') { if (typeof options.callback === 'number') { if (options.callback < 0) { @@ -113,6 +115,10 @@ function noQsMethod (options) { function authorize (options, onConnection) { options = xtend({ decodedPropertyName: 'decoded_token', encodedPropertyName: 'encoded_token' }, options); + if (typeof options.secret !== 'string') { + throw new Error(`Provided secret "${options.secret}" is invalid, must be of type string.`) + } + if (!options.handshake) { return noQsMethod(options); } @@ -163,7 +169,7 @@ function authorize (options, onConnection) { if (options.auth_header_required && !token) { return auth.fail(new UnauthorizedError('missing_authorization_header', { message: 'Server requires Authorization Header' - }), data, accept); + }), socket, accept); } // Get the token from handshake or query string diff --git a/package.json b/package.json index 7118d2a..8a6f993 100644 --- a/package.json +++ b/package.json @@ -39,4 +39,4 @@ "socket.io": "^1.7.3", "socket.io-client": "^1.7.3" } -} \ No newline at end of file +} diff --git a/test/authorizer.test.js b/test/authorizer.test.js index 5dc737f..7b480ee 100644 --- a/test/authorizer.test.js +++ b/test/authorizer.test.js @@ -1,7 +1,7 @@ -var Q = require('q'); -var fixture = require('./fixture'); -var request = require('request'); -var io = require('socket.io-client'); +const Q = require('q'); +const fixture = require('./fixture'); +const request = require('request'); +const io = require('socket.io-client'); describe('authorizer', function() { //start and stop the server @@ -9,19 +9,18 @@ describe('authorizer', function() { after(fixture.stop); describe('when the user is not logged in', function () { - it('should emit error with unauthorized handshake', function (done){ - var socket = io.connect('http://localhost:9000?token=boooooo', { - 'forceNew': true + it('should emit error with unauthorized handshake', function (done) { + const socket = io.connect('http://localhost:9000?token=boooooo', { + forceNew: true }); - socket.on('error', function(err){ + socket.on('error', function(err) { err.message.should.eql("jwt malformed"); err.code.should.eql("invalid_token"); socket.close(); done(); }); }); - }); describe('when the user is logged in', function() { @@ -41,30 +40,35 @@ describe('authorizer', function() { Q.ninvoke(fixture, 'stop') .then(function() { return Q.ninvoke(fixture, 'start', { auth_header_required: true })}) .done(done); - }) + }); + after(function(done) { Q.ninvoke(fixture, 'stop') .then(function() { return Q.ninvoke(fixture, 'start', { })}) .done(done); - }) - - it('auth headers are supported', function (done){ - var socket = io.connect('http://localhost:9000', { - 'forceNew':true, - 'extraHeaders': {'Authorization': 'Bearer ' + this.token} - }); - socket.on('connect', function(){ - socket.close(); - done(); - }).on('error', done); }); - it('auth token in query string is disallowed', function (done){ - var socket = io.connect('http://localhost:9000', { - 'forceNew':true, - 'query': 'token=' + this.token + it('auth headers are supported', function (done) { + const socket = io.connect('http://localhost:9000', { + forceNew: true, + extraHeaders: { Authorization: 'Bearer ' + this.token} }); - socket.on('error', function(err){ + + socket + .on('connect', function() { + socket.close(); + done(); + }) + .on('error', done); + }); + + it('auth token in query string is disallowed', function (done) { + const socket = io.connect('http://localhost:9000', { + forceNew: true, + query: 'token=' + this.token + }); + + socket.on('error', function(err) { err.message.should.eql("Server requires Authorization Header"); err.code.should.eql("missing_authorization_header"); socket.close(); @@ -80,48 +84,58 @@ describe('authorizer', function() { .done(done); }) - it('auth headers are supported', function (done){ - var socket = io.connect('http://localhost:9000', { - 'forceNew':true, - 'extraHeaders': {'Authorization': 'Bearer ' + this.token} + it('auth headers are supported', function (done) { + const socket = io.connect('http://localhost:9000', { + forceNew: true, + extraHeaders: { Authorization: 'Bearer ' + this.token } }); - socket.on('connect', function(){ - socket.close(); - done(); - }).on('error', done); + + socket + .on('connect', function() { + socket.close(); + done(); + }) + .on('error', done); }); - it('should do the handshake and connect', function (done){ - var socket = io.connect('http://localhost:9000', { - 'forceNew':true, - 'query': 'token=' + this.token + it('should do the handshake and connect', function (done) { + const socket = io.connect('http://localhost:9000', { + forceNew: true, + query: 'token=' + this.token }); - socket.on('connect', function(){ - socket.close(); - done(); - }).on('error', done); + + socket + .on('connect', function() { + socket.close(); + done(); + }) + .on('error', done); }); + }); }); - describe('unsgined token', function() { + describe('unsigned token', function() { beforeEach(function () { this.token = 'eyJhbGciOiJub25lIiwiY3R5IjoiSldUIn0.eyJuYW1lIjoiSm9obiBGb28ifQ.'; }); - it('should not do the handshake and connect', function (done){ - var socket = io.connect('http://localhost:9000', { - 'forceNew':true, - 'query': 'token=' + this.token - }); - socket.on('connect', function () { - socket.close(); - done(new Error('this shouldnt happen')); - }).on('error', function (err) { - socket.close(); - err.message.should.eql("jwt signature is required"); - done(); + it('should not do the handshake and connect', function (done) { + const socket = io.connect('http://localhost:9000', { + forceNew: true, + query: 'token=' + this.token }); + + socket + .on('connect', function () { + socket.close(); + done(new Error('this shouldnt happen')); + }) + .on('error', function (err) { + socket.close(); + err.message.should.eql("jwt signature is required"); + done(); + }); }); }); }); diff --git a/test/authorizer_namespaces.test.js b/test/authorizer_namespaces.test.js index 84785b3..1a49fdd 100644 --- a/test/authorizer_namespaces.test.js +++ b/test/authorizer_namespaces.test.js @@ -1,6 +1,6 @@ -var fixture = require('./fixture/namespace'); -var request = require('request'); -var io = require('socket.io-client'); +const fixture = require('./fixture/namespace'); +const request = require('request'); +const io = require('socket.io-client'); describe('authorizer with namespaces', function () { @@ -11,16 +11,17 @@ describe('authorizer with namespaces', function () { describe('when the user is not logged in', function () { - it('should be able to connect to the default namespace', function (done){ - var socket = io.connect('http://localhost:9000'); - socket.once('hi', done); + it('should be able to connect to the default namespace', function (done) { + io.connect('http://localhost:9000') + .once('hi', done); }); - it('should not be able to connect to the admin namespace', function (done){ - var socket = io.connect('http://localhost:9000/admin'); - socket.once('disconnect', function () { - done(); - }); + it('should not be able to connect to the admin namespace', function (done) { + io.connect('http://localhost:9000/admin') + .once('disconnect', function() { done(); }) + .once('hi admin', function() { + done(new Error('unauthenticated client was able to connect to the admin namespace')); + }); }); }); @@ -38,16 +39,10 @@ describe('authorizer with namespaces', function () { }.bind(this)); }); - it('should do the handshake and connect', function (done){ - var socket = io.connect('http://localhost:9000/admin', { - 'forceNew': true, - }); - var token = this.token; - socket.on('connect', function(){ - socket.on('authenticated', function () { - done(); - }).emit('authenticate', { token: token }); - }); + it('should do the handshake and connect', function (done) { + io.connect('http://localhost:9000/admin', { forceNew: true }) + .on('authenticated', done) + .emit('authenticate', { token: this.token }); }); }); diff --git a/test/authorizer_noqs.test.js b/test/authorizer_noqs.test.js index 53ef9ca..61d4f93 100644 --- a/test/authorizer_noqs.test.js +++ b/test/authorizer_noqs.test.js @@ -1,6 +1,6 @@ -var fixture = require('./fixture'); -var request = require('request'); -var io = require('socket.io-client'); +const fixture = require('./fixture'); +const request = require('request'); +const io = require('socket.io-client'); describe('authorizer without querystring', function () { @@ -15,23 +15,17 @@ describe('authorizer without querystring', function () { describe('when the user is not logged in', function () { - it('should close the connection after a timeout if no auth message is received', function (done){ - var socket = io.connect('http://localhost:9000', { - forceNew: true - }); - socket.once('disconnect', function () { - done(); - }); + it('should close the connection after a timeout if no auth message is received', function (done) { + io.connect('http://localhost:9000', { forceNew: true }) + .once('disconnect', function () { done(); }); }); - it('should not respond echo', function (done){ - var socket = io.connect('http://localhost:9000', { - 'forceNew':true, - }); - - socket.on('echo-response', function () { - done(new Error('this should not happen')); - }).emit('echo', { hi: 123 }); + it('should not respond echo', function (done) { + io.connect('http://localhost:9000', { forceNew: true }) + .on('echo-response', function () { + done(new Error('this should not happen')); + }) + .emit('echo', { hi: 123 }); setTimeout(done, 1200); }); @@ -51,19 +45,18 @@ describe('authorizer without querystring', function () { }.bind(this)); }); - it('should do the handshake and connect', function (done){ - var socket = io.connect('http://localhost:9000', { - 'forceNew':true, - }); - var token = this.token; - socket.on('connect', function(){ - socket.on('echo-response', function () { + it('should do the handshake and connect', function (done) { + const socket = io.connect('http://localhost:9000', { forceNew: true }); + + socket + .on('echo-response', function () { socket.close(); done(); - }).on('authenticated', function () { + }) + .on('authenticated', function () { socket.emit('echo'); - }).emit('authenticate', { token: token }); - }); + }) + .emit('authenticate', { token: this.token }); }); }); diff --git a/test/authorizer_secret_function_noqs.test.js b/test/authorizer_secret_function_noqs.test.js index 912eed0..69b3faa 100644 --- a/test/authorizer_secret_function_noqs.test.js +++ b/test/authorizer_secret_function_noqs.test.js @@ -1,6 +1,6 @@ -var fixture = require('./fixture/secret_function'); -var request = require('request'); -var io = require('socket.io-client'); +const fixture = require('./fixture/secret_function'); +const request = require('request'); +const io = require('socket.io-client'); describe('authorizer with secret function', function () { @@ -8,7 +8,7 @@ describe('authorizer with secret function', function () { before(function (done) { fixture.start({ handshake: false - } , done); + }, done); }); after(fixture.stop); @@ -26,20 +26,10 @@ describe('authorizer with secret function', function () { }.bind(this)); }); - it('should emit unauthorized', function (done){ - var socket = io.connect('http://localhost:9000', { - 'forceNew':true, - }); - - var invalidToken = this.invalidToken; - socket.on('unauthorized', function() { - done(); - }); - - socket.on('connect', function(){ - socket - .emit('authenticate', { token: invalidToken + 'ass' }) - }); + it('should emit unauthorized', function (done) { + io.connect('http://localhost:9000', { forceNew: true }) + .on('unauthorized', function() { done(); }) + .emit('authenticate', { token: this.invalidToken + 'ass' }) }); }); @@ -57,20 +47,18 @@ describe('authorizer with secret function', function () { }.bind(this)); }); - it('should do the handshake and connect', function (done){ - var socket = io.connect('http://localhost:9000', { - 'forceNew':true, - }); - var token = this.token; - socket.on('connect', function(){ - socket.on('echo-response', function () { + it('should do the handshake and connect', function (done) { + const socket = io.connect('http://localhost:9000', { forceNew: true }); + + socket + .on('echo-response', function () { socket.close(); done(); - }).on('authenticated', function () { + }) + .on('authenticated', function () { socket.emit('echo'); }) - .emit('authenticate', { token: token }) - }); + .emit('authenticate', { token: this.token }); }); }); diff --git a/test/authorizer_secret_function_qs.test.js b/test/authorizer_secret_function_qs.test.js index 0d3467e..950f0de 100644 --- a/test/authorizer_secret_function_qs.test.js +++ b/test/authorizer_secret_function_qs.test.js @@ -1,6 +1,6 @@ -var fixture = require('./fixture/secret_function'); -var request = require('request'); -var io = require('socket.io-client'); +const fixture = require('./fixture/secret_function'); +const request = require('request'); +const io = require('socket.io-client'); describe('authorizer with secret function', function () { @@ -10,12 +10,10 @@ describe('authorizer with secret function', function () { describe('when the user is not logged in', function () { - it('should emit error with unauthorized handshake', function (done){ - var socket = io.connect('http://localhost:9000?token=boooooo', { - 'forceNew': true - }); + it('should emit error with unauthorized handshake', function (done) { + const socket = io.connect('http://localhost:9000?token=boooooo', { forceNew: true }); - socket.on('error', function(err){ + socket.on('error', function(err) { err.message.should.eql("jwt malformed"); err.code.should.eql("invalid_token"); socket.close(); @@ -37,15 +35,18 @@ describe('authorizer with secret function', function () { }.bind(this)); }); - it('should do the handshake and connect', function (done){ - var socket = io.connect('http://localhost:9000', { - 'forceNew':true, - 'query': 'token=' + this.token + it('should do the handshake and connect', function (done) { + const socket = io.connect('http://localhost:9000', { + forceNew: true, + query: 'token=' + this.token }); - socket.on('connect', function(){ - socket.close(); - done(); - }).on('error', done); + + socket + .on('connect', function() { + socket.close(); + done(); + }) + .on('error', done); }); }); @@ -54,19 +55,22 @@ describe('authorizer with secret function', function () { this.token = 'eyJhbGciOiJub25lIiwiY3R5IjoiSldUIn0.eyJuYW1lIjoiSm9obiBGb28ifQ.'; }); - it('should not do the handshake and connect', function (done){ - var socket = io.connect('http://localhost:9000', { - 'forceNew':true, - 'query': 'token=' + this.token - }); - socket.on('connect', function () { - socket.close(); - done(new Error('this shouldnt happen')); - }).on('error', function (err) { - socket.close(); - err.message.should.eql("jwt signature is required"); - done(); + it('should not do the handshake and connect', function (done) { + const socket = io.connect('http://localhost:9000', { + forceNew: true, + query: 'token=' + this.token }); + + socket + .on('connect', function () { + socket.close(); + done(new Error('this shouldnt happen')); + }) + .on('error', function (err) { + socket.close(); + err.message.should.eql("jwt signature is required"); + done(); + }); }); }); diff --git a/test/fixture/index.js b/test/fixture/index.js index f897021..5a4ef1a 100644 --- a/test/fixture/index.js +++ b/test/fixture/index.js @@ -1,16 +1,15 @@ -var express = require('express'); -var http = require('http'); +const express = require('express'); +const http = require('http'); -var socketIo = require('socket.io'); -var socketio_jwt = require('../../lib'); +const socketIo = require('socket.io'); +const socketio_jwt = require('../../lib'); -var jwt = require('jsonwebtoken'); +const jwt = require('jsonwebtoken'); +const xtend = require('xtend'); +const bodyParser = require('body-parser'); +const enableDestroy = require('server-destroy'); -var xtend = require('xtend'); -var bodyParser = require('body-parser'); - -var server, sio; -var enableDestroy = require('server-destroy'); +let sio; exports.start = function (options, callback) { @@ -25,12 +24,13 @@ exports.start = function (options, callback) { handshake: true }, options); - var app = express(); + const app = express(); + const server = http.createServer(app); + sio = socketIo.listen(server); app.use(bodyParser.json()); - app.post('/login', function (req, res) { - var profile = { + const profile = { first_name: 'John', last_name: 'Doe', email: 'john@doe.com', @@ -38,14 +38,10 @@ exports.start = function (options, callback) { }; // We are sending the profile inside the token - var token = jwt.sign(profile, options.secret, { expiresIn: 60*60*5 }); - + const token = jwt.sign(profile, options.secret, { expiresIn: 60*60*5 }); res.json({token: token}); }); - server = http.createServer(app); - - sio = socketIo.listen(server); if (options.handshake) { sio.use(socketio_jwt.authorize(options)); @@ -77,4 +73,4 @@ exports.stop = function (callback) { server.destroy(); } catch (er) {} callback(); -}; \ No newline at end of file +}; diff --git a/test/fixture/namespace.js b/test/fixture/namespace.js index eff8223..3100f0b 100644 --- a/test/fixture/namespace.js +++ b/test/fixture/namespace.js @@ -1,16 +1,14 @@ -var express = require('express'); -var http = require('http'); +const express = require('express'); +const http = require('http'); -var socketIo = require('socket.io'); -var socketio_jwt = require('../../lib'); +const socketIo = require('socket.io'); +const socketio_jwt = require('../../lib'); -var jwt = require('jsonwebtoken'); +const jwt = require('jsonwebtoken'); +const enableDestroy = require('server-destroy'); +const bodyParser = require('body-parser'); -var xtend = require('xtend'); -var bodyParser = require('body-parser'); - -var server, sio; -var enableDestroy = require('server-destroy'); +let sio; /** * This is an example server that shows how to do namespace authentication. @@ -19,18 +17,19 @@ var enableDestroy = require('server-destroy'); */ exports.start = function (callback) { - options = { + const options = { secret: 'aaafoo super sercret', timeout: 1000, handshake: false }; - var app = express(); + const app = express(); + const server = http.createServer(app); + sio = socketIo.listen(server); app.use(bodyParser.json()); - app.post('/login', function (req, res) { - var profile = { + const profile = { first_name: 'John', last_name: 'Doe', email: 'john@doe.com', @@ -38,20 +37,17 @@ exports.start = function (callback) { }; // We are sending the profile inside the token - var token = jwt.sign(profile, options.secret, { expiresIn: 60*60*5 }); - - res.json({token: token}); + const token = jwt.sign(profile, options.secret, { expiresIn: 60*60*5 }); + res.json({ token: token }); }); - server = http.createServer(app); - sio = socketIo.listen(server); sio.on('connection', function (socket) { socket.emit('hi'); }); - var admin_nsp = sio.of('/admin'); + const admin_nsp = sio.of('/admin'); admin_nsp.on('connection', socketio_jwt.authorize(options)) .on('authenticated', function (socket) { @@ -69,4 +65,4 @@ exports.stop = function (callback) { server.destroy(); } catch (er) {} callback(); -}; \ No newline at end of file +}; diff --git a/test/fixture/secret_function.js b/test/fixture/secret_function.js index 48badd0..f850f24 100644 --- a/test/fixture/secret_function.js +++ b/test/fixture/secret_function.js @@ -1,19 +1,18 @@ -var express = require('express'); -var http = require('http'); +const express = require('express'); +const http = require('http'); -var socketIo = require('socket.io'); -var socketio_jwt = require('../../lib'); +const socketIo = require('socket.io'); +const socketio_jwt = require('../../lib'); -var jwt = require('jsonwebtoken'); +const jwt = require('jsonwebtoken'); +const xtend = require('xtend'); +const bodyParser = require('body-parser'); +const enableDestroy = require('server-destroy'); -var xtend = require('xtend'); -var bodyParser = require('body-parser'); - -var server, sio; -var enableDestroy = require('server-destroy'); +let sio; exports.start = function (options, callback) { - var SECRETS = { + const SECRETS = { 123: 'aaafoo super sercret', 555: 'other' }; @@ -31,12 +30,13 @@ exports.start = function (options, callback) { handshake: true }, options); - var app = express(); + const app = express(); + const server = http.createServer(app); + sio = socketIo.listen(server); app.use(bodyParser.json()); - app.post('/login', function (req, res) { - var profile = { + const profile = { first_name: 'John', last_name: 'Doe', email: 'john@doe.com', @@ -44,15 +44,10 @@ exports.start = function (options, callback) { }; // We are sending the profile inside the token - var token = jwt.sign(profile, SECRETS[123], { expiresIn: 60*60*5 }); - + const token = jwt.sign(profile, SECRETS[123], { expiresIn: 60*60*5 }); res.json({token: token}); }); - server = http.createServer(app); - - sio = socketIo.listen(server); - if (options.handshake) { sio.use(socketio_jwt.authorize(options)); @@ -86,4 +81,3 @@ exports.stop = function (callback) { callback(); }; -