2
1
mirror of https://github.com/Thream/socketio-jwt.git synced 2024-07-06 19:30:11 +02:00

chore: initial commit

This commit is contained in:
divlo 2020-12-27 17:25:44 +01:00
parent 04294c69c5
commit 2e5d281f46
27 changed files with 813 additions and 740 deletions

View File

@ -1,60 +0,0 @@
{
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"contributors": [
{
"login": "beardaway",
"name": "Conrad Sopala",
"avatar_url": "https://avatars3.githubusercontent.com/u/11062800?v=4",
"profile": "https://twitter.com/beardaway",
"contributions": [
"review",
"maintenance"
]
},
{
"login": "Annyv2",
"name": "Annyv2",
"avatar_url": "https://avatars3.githubusercontent.com/u/5016479?v=4",
"profile": "https://github.com/Annyv2",
"contributions": [
"code"
]
},
{
"login": "Amialc",
"name": "Vladyslav Martynets",
"avatar_url": "https://avatars0.githubusercontent.com/u/1114365?v=4",
"profile": "https://github.com/Amialc",
"contributions": [
"code"
]
},
{
"login": "pose",
"name": "Alberto Pose",
"avatar_url": "https://avatars3.githubusercontent.com/u/419703?v=4",
"profile": "https://github.com/pose",
"contributions": [
"code"
]
},
{
"login": "Root-Core",
"name": "Root-Core",
"avatar_url": "https://avatars2.githubusercontent.com/u/5329652?v=4",
"profile": "https://github.com/Root-Core",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
"projectName": "auth0-socketio-jwt",
"projectOwner": "auth0-community",
"repoType": "github",
"repoHost": "https://github.com"
}

11
.editorconfig Normal file
View File

@ -0,0 +1,11 @@
# For more information see: https://editorconfig.org/
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
.idea .idea
node_modules/* node_modules/*

2
.npmrc Normal file
View File

@ -0,0 +1,2 @@
package-lock=false
save-exact=true

View File

@ -1,7 +0,0 @@
language: node_js
node_js:
- 4
- 8
- 10
- 12
- node

2
.yarnrc Normal file
View File

@ -0,0 +1,2 @@
install.no-lockfile true
save-exact true

View File

@ -1,27 +1,36 @@
# Changelog
## [4.0.1] - 2015-04-22 ## [4.0.1] - 2015-04-22
- [4cf0651] Minor. replace regexp for native comparisoon (`Nikita Gusakov`) - [4cf0651] Minor. replace regexp for native comparisoon (`Nikita Gusakov`)
## [4.0.0] - 2015-04-22 ## [4.0.0] - 2015-04-22
- [7e53470] Updated to jsonwebtoken@5.0.0 (`Alberto Pose`) - [7e53470] Updated to jsonwebtoken@5.0.0 (`Alberto Pose`)
## [3.0.0] - 2015-03-16 ## [3.0.0] - 2015-03-16
- [089bfe5] update jsonwebtoken dependency (`José F. Romaniello`) - [089bfe5] update jsonwebtoken dependency (`José F. Romaniello`)
- [dbb4e95] Merge pull request #21 from kennyki/patch-1 (`José F. Romaniello`) - [dbb4e95] Merge pull request #21 from kennyki/patch-1 (`José F. Romaniello`)
- [b1e5530] Added example of handling token expiration (`Kenny Ki`) - [b1e5530] Added example of handling token expiration (`Kenny Ki`)
## [2.3.5] - 2014-09-05 ## [2.3.5] - 2014-09-05
- [f357dd7] update jsonwebtoken (`José F. Romaniello`) - [f357dd7] update jsonwebtoken (`José F. Romaniello`)
## [2.3.4] - 2014-07-16 ## [2.3.4] - 2014-07-16
- [2490770] Merge pull request #18 from yads/master (`José F. Romaniello`) - [2490770] Merge pull request #18 from yads/master (`José F. Romaniello`)
- [cae2123] test fixes (`Vadim Kazakov`) - [cae2123] test fixes (`Vadim Kazakov`)
## [2.3.3] - 2014-07-16 ## [2.3.3] - 2014-07-16
- [55d5e43] Merge branch 'yads-master' (`José F. Romaniello`) - [55d5e43] Merge branch 'yads-master' (`José F. Romaniello`)
- [2897f90] merge (`José F. Romaniello`) - [2897f90] merge (`José F. Romaniello`)
- [1398434] add data to UnauthorizedError so that more information can be returned to client (`Vadim Kazakov`) - [1398434] add data to UnauthorizedError so that more information can be returned to client (`Vadim Kazakov`)
## [2.3.2] - 2014-07-16 ## [2.3.2] - 2014-07-16
- [9d5abf9] update jsonwebtoken module to fix security issue (`José F. Romaniello`) - [9d5abf9] update jsonwebtoken module to fix security issue (`José F. Romaniello`)
- [870a274] update example (`José F. Romaniello`) - [870a274] update example (`José F. Romaniello`)
- [e9b8ea4] fix readme (`José F. Romaniello`) - [e9b8ea4] fix readme (`José F. Romaniello`)
@ -29,28 +38,34 @@
- [e6ea64d] Update README.md (`Mark Rendle`) - [e6ea64d] Update README.md (`Mark Rendle`)
## [2.3.1] - 2014-06-09 ## [2.3.1] - 2014-06-09
- [fe39d2c] Merge pull request #12 from otothea/master (`José F. Romaniello`) - [fe39d2c] Merge pull request #12 from otothea/master (`José F. Romaniello`)
- [f072f91] update readme and fix #11 (`José F. Romaniello`) - [f072f91] update readme and fix #11 (`José F. Romaniello`)
- [29b3882] Make it look for both kinds of query (`Oscar`) - [29b3882] Make it look for both kinds of query (`Oscar`)
- [452cc19] req._query is now req.query (`Oscar`) - [452cc19] req.\_query is now req.query (`Oscar`)
## [2.3.0] - 2014-06-05 (YANKED) ## [2.3.0] - 2014-06-05 (YANKED)
## [2.2.0] - 2014-06-05 (YANKED) ## [2.2.0] - 2014-06-05 (YANKED)
## [2.1.0] - 2014-06-03 ## [2.1.0] - 2014-06-03
- [e8380c1] add support for socket.io 1.0 (`José F. Romaniello`) - [e8380c1] add support for socket.io 1.0 (`José F. Romaniello`)
- [0577d07] missing parenthesis closes #7 (`José F. Romaniello`) - [0577d07] missing parenthesis closes #7 (`José F. Romaniello`)
## [2.0.2] - 2014-03-20 ## [2.0.2] - 2014-03-20
- [9a9f7d0] added newest xtend to prevent deprecation warning from object-keys (`kjellski`) - [9a9f7d0] added newest xtend to prevent deprecation warning from object-keys (`kjellski`)
- [9a58d94] add license, close #2 (`José F. Romaniello`) - [9a58d94] add license, close #2 (`José F. Romaniello`)
- [8e567b9] fix #3 (`José F. Romaniello`) - [8e567b9] fix #3 (`José F. Romaniello`)
## [2.0.1] - 2014-01-23 ## [2.0.1] - 2014-01-23
- [54a33c2] change user to decoded_token (`José F. Romaniello`) - [54a33c2] change user to decoded_token (`José F. Romaniello`)
- [e626188] add example (`José F. Romaniello`) - [e626188] add example (`José F. Romaniello`)
## [2.0.0] - 2014-01-14 ## [2.0.0] - 2014-01-14
- [b292ab7] change the API (`José F. Romaniello`) - [b292ab7] change the API (`José F. Romaniello`)
- [b0f4354] add noqs method (`José F. Romaniello`) - [b0f4354] add noqs method (`José F. Romaniello`)
- [14a34ae] initial commit after fork of passport-socketio (`José F. Romaniello`) - [14a34ae] initial commit after fork of passport-socketio (`José F. Romaniello`)
@ -64,14 +79,17 @@
- [49f35c3] Missing Paren in Example (`Jason Nichols`) - [49f35c3] Missing Paren in Example (`Jason Nichols`)
## [2.2.1] - 2014-01-13 ## [2.2.1] - 2014-01-13
- [efbef7a] move request to devDeps, closes #44 (`José F. Romaniello`) - [efbef7a] move request to devDeps, closes #44 (`José F. Romaniello`)
## [2.2.0] - 2013-11-21 ## [2.2.0] - 2013-11-21
- [bd0980e] Merge pull request #36 from TeamSynergy/cors_workaround (`José F. Romaniello`) - [bd0980e] Merge pull request #36 from TeamSynergy/cors_workaround (`José F. Romaniello`)
- [1a3b3e1] step 2, updated readme (`Screeny`) - [1a3b3e1] step 2, updated readme (`Screeny`)
- [f31dc4a] step 1 (`Screeny`) - [f31dc4a] step 1 (`Screeny`)
## [2.1.2] - 2013-11-18 ## [2.1.2] - 2013-11-18
- [599a614] fixed a security issue (`Amir`) - [599a614] fixed a security issue (`Amir`)
- [91750bb] Merge pull request #33 from TeamSynergy/master (`José F. Romaniello`) - [91750bb] Merge pull request #33 from TeamSynergy/master (`José F. Romaniello`)
- [2d257bf] Update README.md (`Screeny`) - [2d257bf] Update README.md (`Screeny`)

View File

@ -4,13 +4,13 @@ If you are reporting a bug, please fill the sections below (if they are applicab
### Description ### Description
What are you reporting? What are you reporting?
### Expected behaviour ### Expected behaviour
Tell us what you think should happen. Tell us what you think should happen.
### Actual behaviour ### Actual behaviour
Tell us what actually happens. Tell us what actually happens.
@ -32,5 +32,4 @@ Tell us what we should do to reproduce the issue.
Feel free to insert here any screenshots that you consider helpful in solving your issue. Feel free to insert here any screenshots that you consider helpful in solving your issue.
**Filling this, you're helping yourself and repo maintainers in solving your issues quicker! Teamwork makes the dreamwork 🤜🏼🤛🏻** **Filling this, you're helping yourself and repo maintainers in solving your issues quicker! Teamwork makes the dreamwork 🤜🏼🤛🏻**

View File

@ -1,6 +1,6 @@
The MIT License (MIT) MIT License
Copyright (c) 2015 Auth0, Inc. <support@auth0.com> (http://auth0.com) Copyright (c) Auth0, Inc. <support@auth0.com> (http://auth0.com) and Thream contributors
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

View File

@ -18,9 +18,9 @@ Please describe the tests that you ran to verify your changes. Provide any instr
**Test Configuration** **Test Configuration**
* Framework version: - Framework version:
* Language version: - Language version:
* Browser version: - Browser version:
### Additional info ### Additional info

218
README.md
View File

@ -1,9 +1,9 @@
# socketio-jwt # socketio-jwt
[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors) [![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors)
<img src="https://img.shields.io/badge/community-driven-brightgreen.svg"/> <br> <img src="https://img.shields.io/badge/community-driven-brightgreen.svg"/> <br>
### Contributors ## Contributors
Thanks goes to these wonderful people who contribute(d) or maintain(ed) this repo ([emoji key](https://allcontributors.org/docs/en/emoji-key)): Thanks goes to these wonderful people who contribute(d) or maintain(ed) this repo ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@ -38,14 +38,17 @@ npm install socketio-jwt
```javascript ```javascript
// set authorization for socket.io // set authorization for socket.io
io.sockets io.sockets
.on('connection', socketioJwt.authorize({ .on(
secret: 'your secret or public key', 'connection',
timeout: 15000 // 15 seconds to send the authentication message socketioJwt.authorize({
})) secret: 'your secret or public key',
timeout: 15000 // 15 seconds to send the authentication message
})
)
.on('authenticated', (socket) => { .on('authenticated', (socket) => {
//this socket is authenticated, we are good to handle more events from it. //this socket is authenticated, we are good to handle more events from it.
console.log(`hello! ${socket.decoded_token.name}`); console.log(`hello! ${socket.decoded_token.name}`)
}); })
``` ```
**Note:** If you are using a base64-encoded secret (e.g. your Auth0 secret key), you need to convert it to a Buffer: `Buffer('your secret key', 'base64')` **Note:** If you are using a base64-encoded secret (e.g. your Auth0 secret key), you need to convert it to a Buffer: `Buffer('your secret key', 'base64')`
@ -53,7 +56,7 @@ io.sockets
**Client side** **Client side**
```javascript ```javascript
const socket = io.connect('http://localhost:9000'); const socket = io.connect('http://localhost:9000')
socket.on('connect', () => { socket.on('connect', () => {
socket socket
.emit('authenticate', { token: jwt }) //send the jwt .emit('authenticate', { token: jwt }) //send the jwt
@ -61,10 +64,10 @@ socket.on('connect', () => {
//do other things //do other things
}) })
.on('unauthorized', (msg) => { .on('unauthorized', (msg) => {
console.log(`unauthorized: ${JSON.stringify(msg.data)}`); console.log(`unauthorized: ${JSON.stringify(msg.data)}`)
throw new Error(msg.data.type); throw new Error(msg.data.type)
}) })
}); })
``` ```
### One roundtrip ### One roundtrip
@ -72,34 +75,39 @@ socket.on('connect', () => {
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. 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 ```javascript
const io = require('socket.io')(server); const io = require('socket.io')(server)
const socketioJwt = require('socketio-jwt'); const socketioJwt = require('socketio-jwt')
``` ```
With socket.io < 1.0: With socket.io < 1.0:
```javascript ```javascript
io.set('authorization', socketioJwt.authorize({ io.set(
secret: 'your secret or public key', 'authorization',
handshake: true socketioJwt.authorize({
})); secret: 'your secret or public key',
handshake: true
})
)
io.on('connection', (socket) => { io.on('connection', (socket) => {
console.log('hello!', socket.handshake.decoded_token.name); console.log('hello!', socket.handshake.decoded_token.name)
}); })
``` ```
With socket.io >= 1.0: With socket.io >= 1.0:
```javascript ```javascript
io.use(socketioJwt.authorize({ io.use(
secret: 'your secret or public key', socketioJwt.authorize({
handshake: true secret: 'your secret or public key',
})); handshake: true
})
)
io.on('connection', (socket) => { io.on('connection', (socket) => {
console.log('hello!', socket.decoded_token.name); console.log('hello!', socket.decoded_token.name)
}); })
``` ```
For more validation options see [auth0/jsonwebtoken](https://github.com/auth0/node-jsonwebtoken). For more validation options see [auth0/jsonwebtoken](https://github.com/auth0/node-jsonwebtoken).
@ -111,7 +119,7 @@ Append the jwt token using query string:
```javascript ```javascript
const socket = io.connect('http://localhost:9000', { const socket = io.connect('http://localhost:9000', {
query: `token=${your_jwt}` query: `token=${your_jwt}`
}); })
``` ```
Append the jwt token using 'Authorization Header' (Bearer Token): Append the jwt token using 'Authorization Header' (Bearer Token):
@ -119,7 +127,7 @@ Append the jwt token using 'Authorization Header' (Bearer Token):
```javascript ```javascript
const socket = io.connect('http://localhost:9000', { const socket = io.connect('http://localhost:9000', {
extraHeaders: { Authorization: `Bearer ${your_jwt}` } extraHeaders: { Authorization: `Bearer ${your_jwt}` }
}); })
``` ```
Both options can be combined or used optionally. Both options can be combined or used optionally.
@ -131,11 +139,13 @@ Require Bearer Tokens to be passed in as an Authorization Header
**Server side**: **Server side**:
```javascript ```javascript
io.use(socketioJwt.authorize({ io.use(
secret: 'your secret or public key', socketioJwt.authorize({
handshake: true, secret: 'your secret or public key',
auth_header_required: true handshake: true,
})); auth_header_required: true
})
)
``` ```
### Handling token expiration ### Handling token expiration
@ -145,7 +155,7 @@ io.use(socketioJwt.authorize({
When you sign the token with an expiration time (example: 60 minutes): When you sign the token with an expiration time (example: 60 minutes):
```javascript ```javascript
const 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: Your client-side code should handle it as below:
@ -154,11 +164,14 @@ Your client-side code should handle it as below:
```javascript ```javascript
socket.on('unauthorized', (error) => { socket.on('unauthorized', (error) => {
if (error.data.type == 'UnauthorizedError' || error.data.code == 'invalid_token') { if (
error.data.type == 'UnauthorizedError' ||
error.data.code == 'invalid_token'
) {
// redirect user to login page perhaps? // redirect user to login page perhaps?
console.log('User token has expired'); console.log('User token has expired')
} }
}); })
``` ```
### Handling invalid token ### Handling invalid token
@ -175,12 +188,15 @@ Add a callback client-side to execute socket disconnect server-side.
```javascript ```javascript
socket.on('unauthorized', (error, callback) => { socket.on('unauthorized', (error, callback) => {
if (error.data.type == 'UnauthorizedError' || error.data.code == 'invalid_token') { if (
error.data.type == 'UnauthorizedError' ||
error.data.code == 'invalid_token'
) {
// redirect user to login page perhaps or execute callback: // redirect user to login page perhaps or execute callback:
callback(); callback()
console.log('User token has expired'); console.log('User token has expired')
} }
}); })
``` ```
**Server side** **Server side**
@ -188,11 +204,14 @@ socket.on('unauthorized', (error, callback) => {
To disconnect socket server-side without client-side callback: To disconnect socket server-side without client-side callback:
```javascript ```javascript
io.sockets.on('connection', socketioJwt.authorize({ io.sockets.on(
secret: 'secret goes here', 'connection',
// No client-side callback, terminate connection server-side socketioJwt.authorize({
callback: false secret: 'secret goes here',
})) // No client-side callback, terminate connection server-side
callback: false
})
)
``` ```
**Client side** **Client side**
@ -204,11 +223,14 @@ Nothing needs to be changed client-side if callback is false.
To disconnect socket server-side while giving client-side 15 seconds to execute callback: To disconnect socket server-side while giving client-side 15 seconds to execute callback:
```javascript ```javascript
io.sockets.on('connection', socketioJwt.authorize({ io.sockets.on(
secret: 'secret goes here', 'connection',
// Delay server-side socket disconnect to wait for client-side callback socketioJwt.authorize({
callback: 15000 secret: 'secret goes here',
})) // Delay server-side socket disconnect to wait for client-side callback
callback: 15000
})
)
``` ```
Your client-side code should handle it as below: Your client-side code should handle it as below:
@ -217,12 +239,15 @@ Your client-side code should handle it as below:
```javascript ```javascript
socket.on('unauthorized', (error, callback) => { socket.on('unauthorized', (error, callback) => {
if (error.data.type == 'UnauthorizedError' || error.data.code == 'invalid_token') { if (
error.data.type == 'UnauthorizedError' ||
error.data.code == 'invalid_token'
) {
// redirect user to login page perhaps or execute callback: // redirect user to login page perhaps or execute callback:
callback(); callback()
console.log('User token has expired'); console.log('User token has expired')
} }
}); })
``` ```
### Getting the secret dynamically ### Getting the secret dynamically
@ -236,19 +261,21 @@ the provided token.
```javascript ```javascript
const SECRETS = { const SECRETS = {
'user1': 'secret 1', user1: 'secret 1',
'user2': 'secret 2' user2: 'secret 2'
} }
io.use(socketioJwt.authorize({ io.use(
secret: (request, decodedToken, callback) => { socketioJwt.authorize({
// SECRETS[decodedToken.userId] will be used as a secret or secret: (request, decodedToken, callback) => {
// public key for connection user. // SECRETS[decodedToken.userId] will be used as a secret or
// public key for connection user.
callback(null, SECRETS[decodedToken.userId]); callback(null, SECRETS[decodedToken.userId])
}, },
handshake: false handshake: false
})); })
)
``` ```
### Altering the value of the decoded token ### Altering the value of the decoded token
@ -256,22 +283,20 @@ io.use(socketioJwt.authorize({
You can pass a function to change the value of the decoded token You can pass a function to change the value of the decoded token
```javascript ```javascript
io.on( io.on(
'connection', 'connection',
socketIOJwt.authorize({ socketIOJwt.authorize({
customDecoded: (decoded) => { customDecoded: (decoded) => {
return "new decoded token"; return 'new decoded token'
}, },
secret: 'my_secret_key', secret: 'my_secret_key',
decodedPropertyName: 'my_decoded_token', decodedPropertyName: 'my_decoded_token'
}), })
); )
io.on('authenticated', (socket) => { io.on('authenticated', (socket) => {
console.log(socket.my_decoded_token); // new decoded token console.log(socket.my_decoded_token) // new decoded token
}); })
``` ```
## Contribute ## Contribute
@ -281,6 +306,7 @@ Feel like contributing to this repo? We're glad to hear that! Before you start c
Here you can also find the [PR template](https://github.com/auth0-community/socketio-jwt/blob/master/PULL_REQUEST_TEMPLATE.md) to fill once creating a PR. It will automatically appear once you open a pull request. Here you can also find the [PR template](https://github.com/auth0-community/socketio-jwt/blob/master/PULL_REQUEST_TEMPLATE.md) to fill once creating a PR. It will automatically appear once you open a pull request.
You might run the unit tests, before creating a PR: You might run the unit tests, before creating a PR:
```bash ```bash
npm test npm test
``` ```
@ -307,31 +333,33 @@ This project is licensed under the MIT license. See the [LICENSE](https://github
Auth0 helps you to: Auth0 helps you to:
* Add authentication with [multiple authentication sources](https://docs.auth0.com/identityproviders), either social like - Add authentication with [multiple authentication sources](https://docs.auth0.com/identityproviders), either social like
* Google
* Facebook - Google
* Microsoft - Facebook
* Linkedin - Microsoft
* GitHub - Linkedin
* Twitter - GitHub
* Box - Twitter
* Salesforce - Box
* etc. - Salesforce
- etc.
**or** enterprise identity systems like: **or** enterprise identity systems like:
* Windows Azure AD
* Google Apps
* Active Directory
* ADFS
* Any SAML Identity Provider
* Add authentication through more traditional [username/password databases](https://docs.auth0.com/mysql-connection-tutorial) - Windows Azure AD
* Add support for [linking different user accounts](https://docs.auth0.com/link-accounts) with the same user - Google Apps
* Support for generating signed [JSON Web Tokens](https://docs.auth0.com/jwt) to call your APIs and create user identity flow securely - Active Directory
* Analytics of how, when and where users are logging in - ADFS
* Pull data from other sources and add it to user profile, through [JavaScript rules](https://docs.auth0.com/rules) - Any SAML Identity Provider
- Add authentication through more traditional [username/password databases](https://docs.auth0.com/mysql-connection-tutorial)
- Add support for [linking different user accounts](https://docs.auth0.com/link-accounts) with the same user
- Support for generating signed [JSON Web Tokens](https://docs.auth0.com/jwt) to call your APIs and create user identity flow securely
- Analytics of how, when and where users are logging in
- Pull data from other sources and add it to user profile, through [JavaScript rules](https://docs.auth0.com/rules)
## Create a free Auth0 account ## Create a free Auth0 account
* Go to [Auth0 website](https://auth0.com/signup) - Go to [Auth0 website](https://auth0.com/signup)
* Hit the **SIGN UP** button in the upper-right corner - Hit the **SIGN UP** button in the upper-right corner

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

1
example/.gitignore vendored
View File

@ -1,2 +1 @@
node_modules node_modules

View File

@ -1,3 +1,3 @@
# Deprecation Notice # Deprecation Notice
This sample has been deprecated. Please see the [auth0-samples](https://github.com/auth0-samples) org for the latest Auth0 samples. This sample has been deprecated. Please see the [auth0-samples](https://github.com/auth0-samples) org for the latest Auth0 samples.

View File

@ -1,15 +1,15 @@
function UnauthorizedError (code, error) { function UnauthorizedError (code, error) {
Error.call(this, error.message); Error.call(this, error.message)
this.message = error.message; this.message = error.message
this.inner = error; this.inner = error
this.data = { this.data = {
message: this.message, message: this.message,
code: code, code: code,
type: 'UnauthorizedError' type: 'UnauthorizedError'
}; }
} }
UnauthorizedError.prototype = Object.create(Error.prototype); UnauthorizedError.prototype = Object.create(Error.prototype)
UnauthorizedError.prototype.constructor = UnauthorizedError; UnauthorizedError.prototype.constructor = UnauthorizedError
module.exports = UnauthorizedError; module.exports = UnauthorizedError

View File

@ -1,264 +1,301 @@
const xtend = require('xtend'); const xtend = require('xtend')
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken')
const UnauthorizedError = require('./UnauthorizedError'); const UnauthorizedError = require('./UnauthorizedError')
function noQsMethod (options) { function noQsMethod (options) {
const defaults = { required: true }; const defaults = { required: true }
options = xtend(defaults, options); options = xtend(defaults, options)
return (socket) => { return (socket) => {
'use strict'; // Node 4.x workaround 'use strict' // Node 4.x workaround
const server = this.server || socket.server; const server = this.server || socket.server
if (!server.$emit) { if (!server.$emit) {
//then is socket.io 1.0 //then is socket.io 1.0
const Namespace = Object.getPrototypeOf(server.sockets).constructor; const Namespace = Object.getPrototypeOf(server.sockets).constructor
if (!~Namespace.events.indexOf('authenticated')) { if (!~Namespace.events.indexOf('authenticated')) {
Namespace.events.push('authenticated'); Namespace.events.push('authenticated')
} }
} }
let auth_timeout = null; let auth_timeout = null
if (options.required) { if (options.required) {
auth_timeout = setTimeout(() => { auth_timeout = setTimeout(() => {
socket.disconnect('unauthorized'); socket.disconnect('unauthorized')
}, options.timeout || 5000); }, options.timeout || 5000)
} }
socket.on('authenticate', (data) => { socket.on('authenticate', (data) => {
if (options.required) { if (options.required) {
clearTimeout(auth_timeout); clearTimeout(auth_timeout)
} }
// error handler // error handler
const onError = (err, code) => { const onError = (err, code) => {
if (err) { if (err) {
code = code || 'unknown'; code = code || 'unknown'
const error = new UnauthorizedError(code, { const error = new UnauthorizedError(code, {
message: (Object.prototype.toString.call(err) === '[object Object]' && err.message) ? err.message : err message:
}); Object.prototype.toString.call(err) === '[object Object]' &&
err.message
? err.message
: err
})
let callback_timeout; let callback_timeout
// If callback explicitly 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 (
options.callback === false ||
typeof options.callback === 'number'
) {
if (typeof options.callback === 'number') { if (typeof options.callback === 'number') {
if (options.callback < 0) { if (options.callback < 0) {
// If callback is negative(invalid value), make it positive // If callback is negative(invalid value), make it positive
options.callback = Math.abs(options.callback); options.callback = Math.abs(options.callback)
} }
} }
callback_timeout = setTimeout(() => { callback_timeout = setTimeout(
socket.disconnect('unauthorized'); () => {
}, (options.callback === false ? 0 : options.callback)); socket.disconnect('unauthorized')
},
options.callback === false ? 0 : options.callback
)
} }
socket.emit('unauthorized', error, () => { socket.emit('unauthorized', error, () => {
if (typeof options.callback === 'number') { if (typeof options.callback === 'number') {
clearTimeout(callback_timeout); clearTimeout(callback_timeout)
} }
socket.disconnect('unauthorized'); socket.disconnect('unauthorized')
}); })
return; // stop logic, socket will be close on next tick return // stop logic, socket will be close on next tick
} }
}; }
const token = options.cookie ? socket.request.cookies[options.cookie] : (data ? data.token : undefined); const token = options.cookie
? socket.request.cookies[options.cookie]
: data
? data.token
: undefined
if (!token || typeof token !== 'string') { if (!token || typeof token !== 'string') {
return onError({ message: 'invalid token datatype' }, 'invalid_token'); return onError({ message: 'invalid token datatype' }, 'invalid_token')
} }
// Store encoded JWT // Store encoded JWT
socket[options.encodedPropertyName] = token; socket[options.encodedPropertyName] = token
const onJwtVerificationReady = (err, decoded) => { const onJwtVerificationReady = (err, decoded) => {
if (err) { if (err) {
return onError(err, 'invalid_token'); return onError(err, 'invalid_token')
} }
// success handler // success handler
const onSuccess = () => { const onSuccess = () => {
socket[options.decodedPropertyName] = options.customDecoded socket[options.decodedPropertyName] = options.customDecoded
? options.customDecoded(decoded) ? options.customDecoded(decoded)
: decoded; : decoded
socket.emit('authenticated'); socket.emit('authenticated')
if (server.$emit) { if (server.$emit) {
server.$emit('authenticated', socket); server.$emit('authenticated', socket)
} else { } else {
//try getting the current namespace otherwise fallback to all sockets. //try getting the current namespace otherwise fallback to all sockets.
const namespace = (server.nsps && socket.nsp && const namespace =
server.nsps[socket.nsp.name]) || (server.nsps && socket.nsp && server.nsps[socket.nsp.name]) ||
server.sockets; server.sockets
// explicit namespace // explicit namespace
namespace.emit('authenticated', socket); namespace.emit('authenticated', socket)
} }
};
if (options.additional_auth && typeof options.additional_auth === 'function') {
options.additional_auth(decoded, onSuccess, onError);
} else {
onSuccess();
} }
};
if (
options.additional_auth &&
typeof options.additional_auth === 'function'
) {
options.additional_auth(decoded, onSuccess, onError)
} else {
onSuccess()
}
}
const onSecretReady = (err, secret) => { const onSecretReady = (err, secret) => {
if (err || !secret) { if (err || !secret) {
return onError(err, 'invalid_secret'); return onError(err, 'invalid_secret')
} }
jwt.verify(token, secret, options, onJwtVerificationReady); jwt.verify(token, secret, options, onJwtVerificationReady)
}; }
getSecret(socket.request, options.secret, token, onSecretReady); getSecret(socket.request, options.secret, token, onSecretReady)
}); })
}; }
} }
function authorize (options) { function authorize (options) {
options = xtend({ decodedPropertyName: 'decoded_token', encodedPropertyName: 'encoded_token' }, options); options = xtend(
{
decodedPropertyName: 'decoded_token',
encodedPropertyName: 'encoded_token'
},
options
)
if (typeof options.secret !== 'string' && typeof options.secret !== 'function') { if (
throw new Error(`Provided secret ${options.secret} is invalid, must be of type string or function.`); typeof options.secret !== 'string' &&
typeof options.secret !== 'function'
) {
throw new Error(
`Provided secret ${options.secret} is invalid, must be of type string or function.`
)
} }
if (!options.handshake) { if (!options.handshake) {
return noQsMethod(options); return noQsMethod(options)
} }
const defaults = { const defaults = {
success: (socket, accept) => { success: (socket, accept) => {
if (socket.request) { if (socket.request) {
accept(); accept()
} else { } else {
accept(null, true); accept(null, true)
} }
}, },
fail: (error, socket, accept) => { fail: (error, socket, accept) => {
if (socket.request) { if (socket.request) {
accept(error); accept(error)
} else { } else {
accept(null, false); accept(null, false)
} }
} }
}; }
const auth = xtend(defaults, options); const auth = xtend(defaults, options)
return (socket, accept) => { return (socket, accept) => {
'use strict'; // Node 4.x workaround 'use strict' // Node 4.x workaround
let token, error; let token, error
const handshake = socket.handshake; const handshake = socket.handshake
const req = socket.request || socket; const req = socket.request || socket
const authorization_header = (req.headers || {}).authorization; const authorization_header = (req.headers || {}).authorization
if (authorization_header) { if (authorization_header) {
const parts = authorization_header.split(' '); const parts = authorization_header.split(' ')
if (parts.length == 2) { if (parts.length == 2) {
const scheme = parts[0], const scheme = parts[0],
credentials = parts[1]; credentials = parts[1]
if (scheme.toLowerCase() === 'bearer') { if (scheme.toLowerCase() === 'bearer') {
token = credentials; token = credentials
} }
} else { } else {
error = new UnauthorizedError('credentials_bad_format', { error = new UnauthorizedError('credentials_bad_format', {
message: 'Format is Authorization: Bearer [token]' message: 'Format is Authorization: Bearer [token]'
}); })
return auth.fail(error, socket, accept); return auth.fail(error, socket, accept)
} }
} }
// Check if the header has to include authentication // Check if the header has to include authentication
if (options.auth_header_required && !token) { if (options.auth_header_required && !token) {
return auth.fail(new UnauthorizedError('missing_authorization_header', { return auth.fail(
message: 'Server requires Authorization Header' new UnauthorizedError('missing_authorization_header', {
}), socket, accept); message: 'Server requires Authorization Header'
}),
socket,
accept
)
} }
// Get the token from handshake or query string // Get the token from handshake or query string
if (handshake && handshake.query.token) { if (handshake && handshake.query.token) {
token = handshake.query.token; token = handshake.query.token
} } else if (req._query && req._query.token) {
else if (req._query && req._query.token) { token = req._query.token
token = req._query.token; } else if (req.query && req.query.token) {
} token = req.query.token
else if (req.query && req.query.token) {
token = req.query.token;
} }
if (!token) { if (!token) {
error = new UnauthorizedError('credentials_required', { error = new UnauthorizedError('credentials_required', {
message: 'no token provided' message: 'no token provided'
}); })
return auth.fail(error, socket, accept); return auth.fail(error, socket, accept)
} }
// Store encoded JWT // Store encoded JWT
socket[options.encodedPropertyName] = token; socket[options.encodedPropertyName] = token
const onJwtVerificationReady = (err, decoded) => { const onJwtVerificationReady = (err, decoded) => {
if (err) { if (err) {
error = new UnauthorizedError(err.code || 'invalid_token', err); error = new UnauthorizedError(err.code || 'invalid_token', err)
return auth.fail(error, socket, accept); return auth.fail(error, socket, accept)
} }
socket[options.decodedPropertyName] = options.customDecoded socket[options.decodedPropertyName] = options.customDecoded
? options.customDecoded(decoded) ? options.customDecoded(decoded)
: decoded; : decoded
return auth.success(socket, accept); return auth.success(socket, accept)
}; }
const onSecretReady = (err, secret) => { const onSecretReady = (err, secret) => {
if (err) { if (err) {
error = new UnauthorizedError(err.code || 'invalid_secret', err); error = new UnauthorizedError(err.code || 'invalid_secret', err)
return auth.fail(error, socket, accept); return auth.fail(error, socket, accept)
} }
jwt.verify(token, secret, options, onJwtVerificationReady); jwt.verify(token, secret, options, onJwtVerificationReady)
};
getSecret(req, options.secret, token, onSecretReady);
};
}
function getSecret (request, secret, token, callback) {
'use strict'; // Node 4.x workaround
if (typeof secret === 'function') {
if (!token) {
return callback({ code: 'invalid_token', message: 'jwt must be provided' });
} }
const parts = token.split('.'); getSecret(req, options.secret, token, onSecretReady)
if (parts.length < 3) {
return callback({ code: 'invalid_token', message: 'jwt malformed' });
}
if (parts[2].trim() === '') {
return callback({ code: 'invalid_token', message: 'jwt signature is required' });
}
let decodedToken = jwt.decode(token, { complete: true });
if (!decodedToken) {
return callback({ code: 'invalid_token', message: 'jwt malformed' });
}
const arity = secret.length;
if (arity == 4) {
secret(request, decodedToken.header, decodedToken.payload, callback);
} else { // arity == 3
secret(request, decodedToken.payload, callback);
}
} else {
callback(null, secret);
} }
} }
exports.authorize = authorize; function getSecret (request, secret, token, callback) {
exports.UnauthorizedError = UnauthorizedError; 'use strict' // Node 4.x workaround
if (typeof secret === 'function') {
if (!token) {
return callback({
code: 'invalid_token',
message: 'jwt must be provided'
})
}
const parts = token.split('.')
if (parts.length < 3) {
return callback({ code: 'invalid_token', message: 'jwt malformed' })
}
if (parts[2].trim() === '') {
return callback({
code: 'invalid_token',
message: 'jwt signature is required'
})
}
let decodedToken = jwt.decode(token, { complete: true })
if (!decodedToken) {
return callback({ code: 'invalid_token', message: 'jwt malformed' })
}
const arity = secret.length
if (arity == 4) {
secret(request, decodedToken.header, decodedToken.payload, callback)
} else {
// arity == 3
secret(request, decodedToken.payload, callback)
}
} else {
callback(null, secret)
}
}
exports.authorize = authorize
exports.UnauthorizedError = UnauthorizedError

View File

@ -23,20 +23,19 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"jsonwebtoken": "^8.3.0", "jsonwebtoken": "8.3.0",
"xtend": "~2.1.2" "xtend": "2.1.2"
}, },
"devDependencies": { "devDependencies": {
"@types/socket.io": "~1.4.29", "@types/socket.io": "2.1.12",
"body-parser": "~1.17.1", "express": "4.17.1",
"express": "~4.15.2", "mocha": "3.2.0",
"mocha": "~3.2.0", "request": "2.81.0",
"request": "~2.81.0", "serve-static": "1.13.2",
"serve-static": "^1.13.2", "q": "1.5.1",
"q": "^1.5.1", "server-destroy": "1.0.1",
"server-destroy": "~1.0.1", "should": "11.2.1",
"should": "~11.2.1", "socket.io": "2.3.0",
"socket.io": "^1.7.3", "socket.io-client": "2.3.0"
"socket.io-client": "^1.7.3"
} }
} }

View File

@ -1,141 +1,148 @@
const Q = require('q'); const Q = require('q')
const fixture = require('./fixture'); const fixture = require('./fixture')
const request = require('request'); const request = require('request')
const io = require('socket.io-client'); const io = require('socket.io-client')
describe('authorizer', () => { describe('authorizer', () => {
//start and stop the server //start and stop the server
before((done) => { fixture.start({ }, done) }); before((done) => {
after(fixture.stop); fixture.start({}, done)
})
after(fixture.stop)
describe('when the user is not logged in', () => { describe('when the user is not logged in', () => {
it('should emit error with unauthorized handshake', (done) => { it('should emit error with unauthorized handshake', (done) => {
const socket = io.connect('http://localhost:9000?token=boooooo', { const socket = io.connect('http://localhost:9000?token=boooooo', {
forceNew: true forceNew: true
}); })
socket.on('error', (err) => { socket.on('error', (err) => {
err.message.should.eql('jwt malformed'); err.message.should.eql('jwt malformed')
err.code.should.eql('invalid_token'); err.code.should.eql('invalid_token')
socket.close(); socket.close()
done(); done()
}); })
}); })
}); })
describe('when the user is logged in', () => { describe('when the user is logged in', () => {
before((done) => { before((done) => {
request.post({ request.post(
url: 'http://localhost:9000/login', {
form: { username: 'jose', password: 'Pa123' }, url: 'http://localhost:9000/login',
json: true form: { username: 'jose', password: 'Pa123' },
}, (err, resp, body) => { json: true
this.token = body.token; },
done(); (err, resp, body) => {
}); this.token = body.token
}); done()
}
)
})
describe('authorizer disallows query string token when specified in startup options', () => { describe('authorizer disallows query string token when specified in startup options', () => {
before((done) => { before((done) => {
Q.ninvoke(fixture, 'stop') Q.ninvoke(fixture, 'stop')
.then(() => Q.ninvoke(fixture, 'start', { auth_header_required: true })) .then(() =>
.done(done); Q.ninvoke(fixture, 'start', { auth_header_required: true })
}); )
.done(done)
})
after((done) => { after((done) => {
Q.ninvoke(fixture, 'stop')
.then(() => Q.ninvoke(fixture, 'start', { }))
.done(done);
});
it('auth headers are supported', (done) => {
const socket = io.connect('http://localhost:9000', {
forceNew: true,
extraHeaders: { Authorization: 'Bearer ' + this.token}
});
socket
.on('connect', () => {
socket.close();
done();
})
.on('error', done);
});
it('auth token in query string is disallowed', (done) => {
const socket = io.connect('http://localhost:9000', {
forceNew: true,
query: 'token=' + this.token
});
socket.on('error', (err) => {
err.message.should.eql('Server requires Authorization Header');
err.code.should.eql('missing_authorization_header');
socket.close();
done();
});
});
})
describe('authorizer all auth types allowed', () => {
before((done) => {
Q.ninvoke(fixture, 'stop') Q.ninvoke(fixture, 'stop')
.then(() => Q.ninvoke(fixture, 'start', {})) .then(() => Q.ninvoke(fixture, 'start', {}))
.done(done); .done(done)
}) })
it('auth headers are supported', (done) => { it('auth headers are supported', (done) => {
const socket = io.connect('http://localhost:9000', { const socket = io.connect('http://localhost:9000', {
forceNew: true, forceNew: true,
extraHeaders: { Authorization: 'Bearer ' + this.token } extraHeaders: { Authorization: 'Bearer ' + this.token }
}); })
socket socket
.on('connect', () => { .on('connect', () => {
socket.close(); socket.close()
done(); done()
}) })
.on('error', done); .on('error', done)
}); })
it('auth token in query string is disallowed', (done) => {
const socket = io.connect('http://localhost:9000', {
forceNew: true,
query: 'token=' + this.token
})
socket.on('error', (err) => {
err.message.should.eql('Server requires Authorization Header')
err.code.should.eql('missing_authorization_header')
socket.close()
done()
})
})
})
describe('authorizer all auth types allowed', () => {
before((done) => {
Q.ninvoke(fixture, 'stop')
.then(() => Q.ninvoke(fixture, 'start', {}))
.done(done)
})
it('auth headers are supported', (done) => {
const socket = io.connect('http://localhost:9000', {
forceNew: true,
extraHeaders: { Authorization: 'Bearer ' + this.token }
})
socket
.on('connect', () => {
socket.close()
done()
})
.on('error', done)
})
it('should do the handshake and connect', (done) => { it('should do the handshake and connect', (done) => {
const socket = io.connect('http://localhost:9000', { const socket = io.connect('http://localhost:9000', {
forceNew: true, forceNew: true,
query: 'token=' + this.token query: 'token=' + this.token
}); })
socket socket
.on('connect', () => { .on('connect', () => {
socket.close(); socket.close()
done(); done()
}) })
.on('error', done); .on('error', done)
}); })
})
}); })
});
describe('unsigned token', () => { describe('unsigned token', () => {
beforeEach(() => { beforeEach(() => {
this.token = 'eyJhbGciOiJub25lIiwiY3R5IjoiSldUIn0.eyJuYW1lIjoiSm9obiBGb28ifQ.'; this.token =
}); 'eyJhbGciOiJub25lIiwiY3R5IjoiSldUIn0.eyJuYW1lIjoiSm9obiBGb28ifQ.'
})
it('should not do the handshake and connect', (done) => { it('should not do the handshake and connect', (done) => {
const socket = io.connect('http://localhost:9000', { const socket = io.connect('http://localhost:9000', {
forceNew: true, forceNew: true,
query: 'token=' + this.token query: 'token=' + this.token
}); })
socket socket
.on('connect', () => { .on('connect', () => {
socket.close(); socket.close()
done(new Error('this shouldnt happen')); done(new Error('this shouldnt happen'))
}) })
.on('error', (err) => { .on('error', (err) => {
socket.close(); socket.close()
err.message.should.eql('jwt signature is required'); err.message.should.eql('jwt signature is required')
done(); done()
}); })
}); })
}); })
}); })

View File

@ -1,91 +1,108 @@
const fixture = require('./fixture/namespace'); const fixture = require('./fixture/namespace')
const request = require('request'); const request = require('request')
const io = require('socket.io-client'); const io = require('socket.io-client')
describe('authorizer with namespaces', () => { describe('authorizer with namespaces', () => {
//start and stop the server //start and stop the server
before(fixture.start); before(fixture.start)
after(fixture.stop); after(fixture.stop)
describe('when the user is not logged in', () => { describe('when the user is not logged in', () => {
it('should be able to connect to the default namespace', (done) => { it('should be able to connect to the default namespace', (done) => {
io.connect('http://localhost:9000') io.connect('http://localhost:9000')
.once('hi', () => done()) .once('hi', () => done())
.on('error', done); .on('error', done)
}); })
it('should not be able to connect to the admin namespace', (done) => { it('should not be able to connect to the admin namespace', (done) => {
io.connect('http://localhost:9000/admin') io.connect('http://localhost:9000/admin')
.once('disconnect', () => done()) .once('disconnect', () => done())
.once('hi admin', () => done(new Error('unauthenticated client was able to connect to the admin namespace'))); .once('hi admin', () =>
}); done(
new Error(
'unauthenticated client was able to connect to the admin namespace'
)
)
)
})
it('should not be able to connect to the admin_hs namespace', (done) => { it('should not be able to connect to the admin_hs namespace', (done) => {
io.connect('http://localhost:9000/admin_hs') io.connect('http://localhost:9000/admin_hs')
.once('hi admin', () => done(new Error('unauthenticated client was able to connect to the admin_hs namespace'))) .once('hi admin', () =>
done(
new Error(
'unauthenticated client was able to connect to the admin_hs namespace'
)
)
)
.on('error', (err) => { .on('error', (err) => {
if (err === 'Invalid namespace') { // SocketIO throws this error, if auth failed if (err === 'Invalid namespace') {
return; // SocketIO throws this error, if auth failed
return
} else if (err && err.type == 'UnauthorizedError') { } else if (err && err.type == 'UnauthorizedError') {
done(); done()
} else { } else {
done(err); done(err)
} }
}); })
}); })
})
});
describe('when the user is logged in', () => { describe('when the user is logged in', () => {
beforeEach((done) => { beforeEach((done) => {
request.post({ request.post(
url: 'http://localhost:9000/login', {
form: { username: 'jose', password: 'Pa123' }, url: 'http://localhost:9000/login',
json: true form: { username: 'jose', password: 'Pa123' },
}, (err, resp, body) => { json: true
this.token = body.token; },
done(); (err, resp, body) => {
}); this.token = body.token
}); done()
}
)
})
it('should do the authentication and connect', (done) => { it('should do the authentication and connect', (done) => {
io.connect('http://localhost:9000/admin', { forceNew: true }) io.connect('http://localhost:9000/admin', { forceNew: true })
.on('hi admin', () => done()) .on('hi admin', () => done())
.emit('authenticate', { token: this.token }); .emit('authenticate', { token: this.token })
}); })
it('should do the authentication and connect without "forceNew"', (done) => { it('should do the authentication and connect without "forceNew"', (done) => {
io.connect('http://localhost:9000/admin', { forceNew: false }) io.connect('http://localhost:9000/admin', { forceNew: false })
.on('hi admin', () => done()) .on('hi admin', () => done())
.emit('authenticate', { token: this.token }); .emit('authenticate', { token: this.token })
}); })
}); })
describe('when the user is logged in via handshake', () => { describe('when the user is logged in via handshake', () => {
beforeEach((done) => { beforeEach((done) => {
request.post({ request.post(
url: 'http://localhost:9000/login', {
form: { username: 'jose', password: 'Pa123' }, url: 'http://localhost:9000/login',
json: true form: { username: 'jose', password: 'Pa123' },
}, (err, resp, body) => { json: true
this.token = body.token; },
done(); (err, resp, body) => {
}); this.token = body.token
}); done()
}
)
})
it('should do the handshake and connect', (done) => { it('should do the handshake and connect', (done) => {
io.connect('http://localhost:9000/admin_hs', { forceNew: true, query: 'token=' + this.token }) io.connect('http://localhost:9000/admin_hs', {
.once('hi admin', () => done()); forceNew: true,
}); query: 'token=' + this.token
}).once('hi admin', () => done())
})
it('should do the handshake and connect without "forceNew"', (done) => { it('should do the handshake and connect without "forceNew"', (done) => {
io.connect('http://localhost:9000/admin_hs', { forceNew: false, query: 'token=' + this.token }) io.connect('http://localhost:9000/admin_hs', {
.once('hi admin', () => done()); forceNew: false,
}); query: 'token=' + this.token
}); }).once('hi admin', () => done())
})
}); })
})

View File

@ -1,57 +1,59 @@
const fixture = require('./fixture'); const fixture = require('./fixture')
const request = require('request'); const request = require('request')
const io = require('socket.io-client'); const io = require('socket.io-client')
describe('authorizer without querystring', () => { describe('authorizer without querystring', () => {
//start and stop the server //start and stop the server
before((done) => { before((done) => {
fixture.start({ handshake: false }, done); fixture.start({ handshake: false }, done)
}); })
after(fixture.stop); after(fixture.stop)
describe('when the user is not logged in', () => { describe('when the user is not logged in', () => {
it('should close the connection after a timeout if no auth message is received', (done) => { it('should close the connection after a timeout if no auth message is received', (done) => {
io.connect('http://localhost:9000', { forceNew: true }) io.connect('http://localhost:9000', { forceNew: true }).once(
.once('disconnect', () => done()); 'disconnect',
}); () => done()
)
})
it('should not respond echo', (done) => { it('should not respond echo', (done) => {
io.connect('http://localhost:9000', { forceNew: true }) io.connect('http://localhost:9000', { forceNew: true })
.on('echo-response', () => done(new Error('this should not happen'))) .on('echo-response', () => done(new Error('this should not happen')))
.emit('echo', { hi: 123 }); .emit('echo', { hi: 123 })
setTimeout(done, 1200); setTimeout(done, 1200)
}); })
})
});
describe('when the user is logged in', () => { describe('when the user is logged in', () => {
beforeEach((done) => { beforeEach((done) => {
request.post({ request.post(
url: 'http://localhost:9000/login', {
form: { username: 'jose', password: 'Pa123' }, url: 'http://localhost:9000/login',
json: true form: { username: 'jose', password: 'Pa123' },
}, (err, resp, body) => { json: true
this.token = body.token; },
done(); (err, resp, body) => {
}); this.token = body.token
}); done()
}
)
})
it('should do the authentication and connect', (done) => { it('should do the authentication and connect', (done) => {
const socket = io.connect('http://localhost:9000', { forceNew: true }); const socket = io.connect('http://localhost:9000', { forceNew: true })
socket socket
.on('echo-response', () => { .on('echo-response', () => {
socket.close(); socket.close()
done(); done()
}) })
.on('authenticated', () => { socket.emit('echo'); }) .on('authenticated', () => {
.emit('authenticate', { token: this.token }); socket.emit('echo')
}); })
}); .emit('authenticate', { token: this.token })
})
}); })
})

View File

@ -1,63 +1,69 @@
const fixture = require('./fixture/secret_function'); const fixture = require('./fixture/secret_function')
const request = require('request'); const request = require('request')
const io = require('socket.io-client'); const io = require('socket.io-client')
describe('authorizer with secret function', () => { describe('authorizer with secret function', () => {
//start and stop the server //start and stop the server
before((done) => { before((done) => {
fixture.start({ fixture.start(
handshake: false {
}, done); handshake: false
}); },
done
)
})
after(fixture.stop); after(fixture.stop)
describe('when the user is not logged in', () => { describe('when the user is not logged in', () => {
describe('and when token is not valid', () => { describe('and when token is not valid', () => {
beforeEach((done) => { beforeEach((done) => {
request.post({ request.post(
url: 'http://localhost:9000/login', {
json: { username: 'invalid_signature', password: 'Pa123' } url: 'http://localhost:9000/login',
}, (err, resp, body) => { json: { username: 'invalid_signature', password: 'Pa123' }
this.invalidToken = body.token; },
done(); (err, resp, body) => {
}); this.invalidToken = body.token
}); done()
}
)
})
it('should emit unauthorized', (done) => { it('should emit unauthorized', (done) => {
io.connect('http://localhost:9000', { forceNew: true }) io.connect('http://localhost:9000', { forceNew: true })
.on('unauthorized', () => done()) .on('unauthorized', () => done())
.emit('authenticate', { token: this.invalidToken + 'ass' }) .emit('authenticate', { token: this.invalidToken + 'ass' })
}); })
}); })
})
});
describe('when the user is logged in', () => { describe('when the user is logged in', () => {
beforeEach((done) => { beforeEach((done) => {
request.post({ request.post(
url: 'http://localhost:9000/login', {
json: { username: 'valid_signature', password: 'Pa123' } url: 'http://localhost:9000/login',
}, (err, resp, body) => { json: { username: 'valid_signature', password: 'Pa123' }
this.token = body.token; },
done(); (err, resp, body) => {
}); this.token = body.token
}); done()
}
)
})
it('should do the authentication and connect', (done) => { it('should do the authentication and connect', (done) => {
const socket = io.connect('http://localhost:9000', { forceNew: true }); const socket = io.connect('http://localhost:9000', { forceNew: true })
socket socket
.on('echo-response', () => { .on('echo-response', () => {
socket.close(); socket.close()
done(); done()
}) })
.on('authenticated', () => { socket.emit('echo'); }) .on('authenticated', () => {
.emit('authenticate', { token: this.token }); socket.emit('echo')
}); })
}); .emit('authenticate', { token: this.token })
})
}); })
})

View File

@ -1,77 +1,78 @@
const fixture = require('./fixture/secret_function'); const fixture = require('./fixture/secret_function')
const request = require('request'); const request = require('request')
const io = require('socket.io-client'); const io = require('socket.io-client')
describe('authorizer with secret function', () => { describe('authorizer with secret function', () => {
//start and stop the server //start and stop the server
before(fixture.start); before(fixture.start)
after(fixture.stop); after(fixture.stop)
describe('when the user is not logged in', () => { describe('when the user is not logged in', () => {
it('should emit error with unauthorized handshake', (done) => { it('should emit error with unauthorized handshake', (done) => {
const socket = io.connect('http://localhost:9000?token=boooooo', { forceNew: true }); const socket = io.connect('http://localhost:9000?token=boooooo', {
forceNew: true
})
socket.on('error', (err) => { socket.on('error', (err) => {
err.message.should.eql('jwt malformed'); err.message.should.eql('jwt malformed')
err.code.should.eql('invalid_token'); err.code.should.eql('invalid_token')
socket.close(); socket.close()
done(); done()
}); })
}); })
})
});
describe('when the user is logged in', () => { describe('when the user is logged in', () => {
beforeEach((done) => { beforeEach((done) => {
request.post({ request.post(
url: 'http://localhost:9000/login', {
json: { username: 'valid_signature', password: 'Pa123' } url: 'http://localhost:9000/login',
}, (err, resp, body) => { json: { username: 'valid_signature', password: 'Pa123' }
this.token = body.token; },
done(); (err, resp, body) => {
}); this.token = body.token
}); done()
}
)
})
it('should do the handshake and connect', (done) => { it('should do the handshake and connect', (done) => {
const socket = io.connect('http://localhost:9000', { const socket = io.connect('http://localhost:9000', {
forceNew: true, forceNew: true,
query: 'token=' + this.token query: 'token=' + this.token
}); })
socket socket
.on('connect', () => { .on('connect', () => {
socket.close(); socket.close()
done(); done()
}) })
.on('error', done); .on('error', done)
}); })
}); })
describe('unsigned token', () => { describe('unsigned token', () => {
beforeEach(() => { beforeEach(() => {
this.token = 'eyJhbGciOiJub25lIiwiY3R5IjoiSldUIn0.eyJuYW1lIjoiSm9obiBGb28ifQ.'; this.token =
}); 'eyJhbGciOiJub25lIiwiY3R5IjoiSldUIn0.eyJuYW1lIjoiSm9obiBGb28ifQ.'
})
it('should not do the handshake and connect', (done) => { it('should not do the handshake and connect', (done) => {
const socket = io.connect('http://localhost:9000', { const socket = io.connect('http://localhost:9000', {
forceNew: true, forceNew: true,
query: 'token=' + this.token query: 'token=' + this.token
}); })
socket socket
.on('connect', () => { .on('connect', () => {
socket.close(); socket.close()
done(new Error('this shouldnt happen')); done(new Error('this shouldnt happen'))
}) })
.on('error', (err) => { .on('error', (err) => {
socket.close(); socket.close()
err.message.should.eql('jwt signature is required'); err.message.should.eql('jwt signature is required')
done(); done()
}); })
}); })
}); })
})
});

View File

@ -1,78 +1,78 @@
'use strict'; // Node 4.x workaround 'use strict' // Node 4.x workaround
const express = require('express'); const express = require('express')
const http = require('http'); const http = require('http')
const socketIo = require('socket.io'); const socketIo = require('socket.io')
const socketio_jwt = require('../../lib'); const socketio_jwt = require('../../lib')
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken')
const xtend = require('xtend'); const xtend = require('xtend')
const bodyParser = require('body-parser'); const enableDestroy = require('server-destroy')
const enableDestroy = require('server-destroy');
let sio; let sio
exports.start = (options, callback) => { exports.start = (options, callback) => {
if (typeof options == 'function') { if (typeof options == 'function') {
callback = options; callback = options
options = {}; options = {}
} }
options = xtend({ options = xtend(
secret: 'aaafoo super sercret', {
timeout: 1000, secret: 'aaafoo super sercret',
handshake: true timeout: 1000,
}, options); handshake: true
},
options
)
const app = express(); const app = express()
const server = http.createServer(app); const server = http.createServer(app)
sio = socketIo.listen(server); sio = socketIo.listen(server)
app.use(bodyParser.json()); app.use(express.json())
app.post('/login', (req, res) => { app.post('/login', (req, res) => {
const profile = { const profile = {
first_name: 'John', first_name: 'John',
last_name: 'Doe', last_name: 'Doe',
email: 'john@doe.com', email: 'john@doe.com',
id: 123 id: 123
}; }
// We are sending the profile inside the token // We are sending the profile inside the token
const 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}); res.json({ token: token })
}); })
if (options.handshake) { if (options.handshake) {
sio.use(socketio_jwt.authorize(options)); sio.use(socketio_jwt.authorize(options))
sio.sockets.on('echo', (m) => { sio.sockets.on('echo', (m) => {
sio.sockets.emit('echo-response', m); sio.sockets.emit('echo-response', m)
}); })
} else { } else {
sio.sockets sio.sockets
.on('connection', socketio_jwt.authorize(options)) .on('connection', socketio_jwt.authorize(options))
.on('authenticated', (socket) => { .on('authenticated', (socket) => {
socket.on('echo', (m) => { socket.on('echo', (m) => {
socket.emit('echo-response', m); socket.emit('echo-response', m)
}); })
}); })
} }
server.__sockets = []; server.__sockets = []
server.on('connection', (c) => { server.on('connection', (c) => {
server.__sockets.push(c); server.__sockets.push(c)
}); })
server.listen(9000, callback); server.listen(9000, callback)
enableDestroy(server); enableDestroy(server)
}; }
exports.stop = (callback) => { exports.stop = (callback) => {
sio.close(); sio.close()
try { try {
server.destroy(); server.destroy()
} catch (er) {} } catch (er) {}
callback(); callback()
}; }

View File

@ -1,17 +1,16 @@
'use strict'; // Node 4.x workaround 'use strict' // Node 4.x workaround
const express = require('express'); const express = require('express')
const http = require('http'); const http = require('http')
const socketIo = require('socket.io'); const socketIo = require('socket.io')
const socketio_jwt = require('../../lib'); const socketio_jwt = require('../../lib')
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken')
const xtend = require('xtend'); const xtend = require('xtend')
const enableDestroy = require('server-destroy'); const enableDestroy = require('server-destroy')
const bodyParser = require('body-parser');
let sio; let sio
/** /**
* This is an example server that shows how to do namespace authentication. * This is an example server that shows how to do namespace authentication.
@ -19,62 +18,60 @@ let sio;
* The /admin namespace is protected by JWTs while the global namespace is public. * The /admin namespace is protected by JWTs while the global namespace is public.
*/ */
exports.start = (callback) => { exports.start = (callback) => {
const options = { const options = {
secret: 'aaafoo super sercret', secret: 'aaafoo super sercret',
timeout: 1000, timeout: 1000,
handshake: false handshake: false
}; }
const app = express(); const app = express()
const server = http.createServer(app); const server = http.createServer(app)
sio = socketIo.listen(server); sio = socketIo.listen(server)
app.use(bodyParser.json()); app.use(express.json())
app.post('/login', (req, res) => { app.post('/login', (req, res) => {
const profile = { const profile = {
first_name: 'John', first_name: 'John',
last_name: 'Doe', last_name: 'Doe',
email: 'john@doe.com', email: 'john@doe.com',
id: 123 id: 123
}; }
// We are sending the profile inside the token // We are sending the profile inside the token
const 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 }); res.json({ token: token })
}); })
// Global namespace (public) // Global namespace (public)
sio.on('connection', (socket) => { sio.on('connection', (socket) => {
socket.emit('hi'); socket.emit('hi')
}); })
// Second roundtrip // Second roundtrip
const admin_nsp = sio.of('/admin'); const admin_nsp = sio.of('/admin')
admin_nsp.on('connection', socketio_jwt.authorize(options)) admin_nsp
.on('connection', socketio_jwt.authorize(options))
.on('authenticated', (socket) => { .on('authenticated', (socket) => {
socket.emit('hi admin'); socket.emit('hi admin')
}); })
// One roundtrip // One roundtrip
const admin_nsp_hs = sio.of('/admin_hs'); const admin_nsp_hs = sio.of('/admin_hs')
admin_nsp_hs.use(socketio_jwt.authorize(xtend(options, { handshake: true }))); admin_nsp_hs.use(socketio_jwt.authorize(xtend(options, { handshake: true })))
admin_nsp_hs.on('connection', (socket) => { admin_nsp_hs.on('connection', (socket) => {
socket.emit('hi admin'); socket.emit('hi admin')
}); })
server.listen(9000, callback)
server.listen(9000, callback); enableDestroy(server)
enableDestroy(server); }
};
exports.stop = (callback) => { exports.stop = (callback) => {
sio.close(); sio.close()
try { try {
server.destroy(); server.destroy()
} catch (er) {} } catch (er) {}
callback(); callback()
}; }

View File

@ -1,85 +1,87 @@
'use strict'; // Node 4.x workaround 'use strict' // Node 4.x workaround
const express = require('express'); const express = require('express')
const http = require('http'); const http = require('http')
const socketIo = require('socket.io'); const socketIo = require('socket.io')
const socketio_jwt = require('../../lib'); const socketio_jwt = require('../../lib')
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken')
const xtend = require('xtend'); const xtend = require('xtend')
const bodyParser = require('body-parser'); const enableDestroy = require('server-destroy')
const enableDestroy = require('server-destroy');
let sio; let sio
exports.start = (options, callback) => { exports.start = (options, callback) => {
const SECRETS = { const SECRETS = {
123: 'aaafoo super sercret', 123: 'aaafoo super sercret',
555: 'other' 555: 'other'
};
if (typeof options == 'function') {
callback = options;
options = {};
} }
options = xtend({ if (typeof options == 'function') {
secret: (request, decodedToken, callback) => { callback = options
callback(null, SECRETS[decodedToken.id]); options = {}
}
options = xtend(
{
secret: (request, decodedToken, callback) => {
callback(null, SECRETS[decodedToken.id])
},
timeout: 1000,
handshake: true
}, },
timeout: 1000, options
handshake: true )
}, options);
const app = express(); const app = express()
const server = http.createServer(app); const server = http.createServer(app)
sio = socketIo.listen(server); sio = socketIo.listen(server)
app.use(bodyParser.json()); app.use(express.json())
app.post('/login', (req, res) => { app.post('/login', (req, res) => {
const profile = { const profile = {
first_name: 'John', first_name: 'John',
last_name: 'Doe', last_name: 'Doe',
email: 'john@doe.com', email: 'john@doe.com',
id: req.body.username === 'valid_signature' ? 123 : 555 id: req.body.username === 'valid_signature' ? 123 : 555
}; }
// We are sending the profile inside the token // We are sending the profile inside the token
const 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}); res.json({ token: token })
}); })
if (options.handshake) { if (options.handshake) {
sio.use(socketio_jwt.authorize(options)); sio.use(socketio_jwt.authorize(options))
sio.sockets.on('echo', (m) => { sio.sockets.on('echo', (m) => {
sio.sockets.emit('echo-response', m); sio.sockets.emit('echo-response', m)
}); })
} else { } else {
sio.sockets sio.sockets
.on('connection', socketio_jwt.authorize(options)) .on('connection', socketio_jwt.authorize(options))
.on('authenticated', (socket) => { .on('authenticated', (socket) => {
socket.on('echo', (m) => { socket.on('echo', (m) => {
socket.emit('echo-response', m); socket.emit('echo-response', m)
}); })
}); })
} }
server.__sockets = []; server.__sockets = []
server.on('connection', (c) => { server.on('connection', (c) => {
server.__sockets.push(c); server.__sockets.push(c)
}); })
server.listen(9000, callback); server.listen(9000, callback)
enableDestroy(server); enableDestroy(server)
}; }
exports.stop = (callback) => { exports.stop = (callback) => {
sio.close(); sio.close()
try { try {
server.destroy(); server.destroy()
} catch (er) {} } catch (er) {}
callback(); callback()
}; }

View File

@ -1,3 +1,3 @@
--require should --require should
--reporter spec --reporter spec
--timeout 15000 --timeout 15000

63
types/index.d.ts vendored
View File

@ -6,68 +6,81 @@
/// <reference types="socket.io" /> /// <reference types="socket.io" />
declare module 'socketio-jwt' { declare module 'socketio-jwt' {
/** /**
* Defines possible errors for the secret-callback. * Defines possible errors for the secret-callback.
*/ */
interface ISocketIOError { interface ISocketIOError {
readonly code: string; readonly code: string
readonly message: string; readonly message: string
} }
/** /**
* Callback gets called, if secret is given dynamically. * Callback gets called, if secret is given dynamically.
*/ */
interface ISocketCallback { interface ISocketCallback {
(err: ISocketIOError, success: string): void; (err: ISocketIOError, success: string): void
} }
interface ISocketIOMiddleware { interface ISocketIOMiddleware {
(socket: SocketIO.Socket, fn: (err?: any) => void): void; (socket: SocketIO.Socket, fn: (err?: any) => void): void
} }
interface IOptions { interface IOptions {
additional_auth?: (decoded: object, onSuccess: () => void, onError: (err: (string | ISocketIOError), code: string) => void) => void; additional_auth?: (
customDecoded?: (decoded: object) => object; decoded: object,
onSuccess: () => void,
onError: (err: string | ISocketIOError, code: string) => void
) => void
customDecoded?: (decoded: object) => object
callback?: (false | number); callback?: false | number
secret: (string | ((request: any, decodedToken: object, callback: ISocketCallback) => void)); secret:
| string
| ((
request: any,
decodedToken: object,
callback: ISocketCallback
) => void)
encodedPropertyName?: string; encodedPropertyName?: string
decodedPropertyName?: string; decodedPropertyName?: string
auth_header_required?: boolean; auth_header_required?: boolean
handshake?: boolean; handshake?: boolean
required?: boolean; required?: boolean
timeout?: number; timeout?: number
cookie?: string; cookie?: string
} }
function authorize(options: IOptions/*, onConnection: Function*/): ISocketIOMiddleware; function authorize(
options: IOptions /*, onConnection: Function*/
): ISocketIOMiddleware
interface UnauthorizedError extends Error { interface UnauthorizedError extends Error {
readonly message: string; readonly message: string
readonly inner: object; readonly inner: object
readonly data: { message: string, code: string, type: 'UnauthorizedError' } readonly data: { message: string; code: string; type: 'UnauthorizedError' }
} }
var UnauthorizedError: { var UnauthorizedError: {
prototype: UnauthorizedError; prototype: UnauthorizedError
new (code: string, error: { message: string }): UnauthorizedError; new (code: string, error: { message: string }): UnauthorizedError
} }
/** /**
* This is an augmented version of the SocketIO.Server. * This is an augmented version of the SocketIO.Server.
* It knows the 'authenticated' event and should be extended in future. * It knows the 'authenticated' event and should be extended in future.
* @see SocketIO.Server * @see SocketIO.Server
*/ */
export interface JWTServer extends SocketIO.Server { export interface JWTServer extends SocketIO.Server {
/** /**
* The event gets fired when a new connection is authenticated via JWT. * The event gets fired when a new connection is authenticated via JWT.
* @param event The event being fired: 'authenticated' * @param event The event being fired: 'authenticated'
* @param listener A listener that should take one parameter of type Socket * @param listener A listener that should take one parameter of type Socket
* @return The default '/' Namespace * @return The default '/' Namespace
*/ */
on(event: ('authenticated' | string), listener: (socket: SocketIO.Socket) => void): SocketIO.Namespace; on(
event: 'authenticated' | string,
listener: (socket: SocketIO.Socket) => void
): SocketIO.Namespace
} }
} }