├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── lib └── index.js ├── package.json └── test ├── jwt.test.js ├── multitenancy.test.js ├── revocation.test.js └── string_token.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | 5 | indent_style = space 6 | indent_size = 2 7 | 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/* 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: "node_js" 2 | before_install: npm i -g npm@2 3 | node_js: 4 | - "0.10" 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Vikrant Varma 4 | 5 | For parts of restify-jwt originating from the express-jwt 6 | repository (https://github.com/auth0/express-jwt): 7 | 8 | Copyright (c) 2015 Auth0, Inc. (http://auth0.com) 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # restify-jwt 2 | 3 | [![NPM](https://img.shields.io/npm/v/restify-jwt.svg)](https://www.npmjs.com/package/restify-jwt) 4 | [![Build Status](https://travis-ci.org/amrav/restify-jwt.svg)](https://travis-ci.org/amrav/restify-jwt) 5 | 6 | [Restify](http://mcavage.me/node-restify/) middleware that validates JsonWebTokens and sets `req.user`. 7 | 8 | This module lets you authenticate HTTP requests using JWT tokens in your restify applications. 9 | 10 | ## Install 11 | 12 | $ npm install restify-jwt 13 | 14 | ## Usage 15 | 16 | The JWT authentication middleware authenticates callers using a JWT. 17 | If the token is valid, `req.user` will be set with the JSON object decoded 18 | to be used by later middleware for authorization and access control. 19 | 20 | For example, 21 | 22 | ```javascript 23 | var jwt = require('restify-jwt'); 24 | 25 | app.get('/protected', 26 | jwt({secret: 'shhhhhhared-secret'}), 27 | function(req, res) { 28 | if (!req.user.admin) return res.send(401); 29 | res.send(200); 30 | }); 31 | ``` 32 | 33 | You can specify audience and/or issuer as well: 34 | 35 | ```javascript 36 | jwt({ secret: 'shhhhhhared-secret', 37 | audience: 'http://myapi/protected', 38 | issuer: 'http://issuer' }) 39 | ``` 40 | 41 | > If the JWT has an expiration (`exp`), it will be checked. 42 | 43 | If you are using a base64 URL-encoded secret, pass a `Buffer` with `base64` encoding as the secret instead of a string: 44 | 45 | ```javascript 46 | jwt({ secret: new Buffer('shhhhhhared-secret', 'base64') }) 47 | ``` 48 | 49 | Optionally you can make some paths unprotected as follows: 50 | 51 | ```javascript 52 | app.use(jwt({ secret: 'shhhhhhared-secret'}).unless({path: ['/token']})); 53 | ``` 54 | 55 | This is especially useful when applying to multiple routes. In the example above, `path` can be a string, a regexp, or an array of any of those. 56 | 57 | > For more details on the `.unless` syntax including additional options, please see [express-unless](https://github.com/jfromaniello/express-unless). 58 | 59 | This module also support tokens signed with public/private key pairs. Instead of a secret, you can specify a Buffer with the public key 60 | 61 | ```javascript 62 | var publicKey = fs.readFileSync('/pat/to/public.pub'); 63 | jwt({ secret: publicKey }); 64 | ``` 65 | 66 | By default, the decoded token is attached to `req.user` but can be configured with the `requestProperty` option. 67 | 68 | 69 | ```javascript 70 | jwt({ secret: publicKey, requestProperty: 'auth' }); 71 | ``` 72 | 73 | A custom function for extracting the token from a request can be specified with 74 | the `getToken` option. This is useful if you need to pass the token through a 75 | query parameter or a cookie. You can throw an error in this function and it will 76 | be handled by `restify-jwt`. 77 | 78 | ```javascript 79 | app.use(jwt({ 80 | secret: 'hello world !', 81 | credentialsRequired: false, 82 | getToken: function fromHeaderOrQuerystring (req) { 83 | if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { 84 | return req.headers.authorization.split(' ')[1]; 85 | } else if (req.query && req.query.token) { 86 | return req.query.token; 87 | } 88 | return null; 89 | } 90 | })); 91 | ``` 92 | 93 | ### Multi-tenancy 94 | If you are developing an application in which the secret used to sign tokens is not static, you can provide a callback function as the `secret` parameter. The function has the signature: `function(req, payload, done)`: 95 | * `req` (`Object`) - The restify `request` object. 96 | * `payload` (`Object`) - An object with the JWT claims. 97 | * `done` (`Function`) - A function with signature `function(err, secret)` to be invoked when the secret is retrieved. 98 | * `err` (`Any`) - The error that occurred. 99 | * `secret` (`String`) - The secret to use to verify the JWT. 100 | 101 | For example, if the secret varies based on the [JWT issuer](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#issDef): 102 | ```javascript 103 | var jwt = require('restify-jwt'); 104 | var data = require('./data'); 105 | var utilities = require('./utilities'); 106 | 107 | var secretCallback = function(req, payload, done){ 108 | var issuer = payload.iss; 109 | 110 | data.getTenantByIdentifier(issuer, function(err, tenant){ 111 | if (err) { return done(err); } 112 | if (!tenant) { return done(new Error('missing_secret')); } 113 | 114 | var secret = utilities.decrypt(tenant.secret); 115 | done(null, secret); 116 | }); 117 | }; 118 | 119 | app.get('/protected', 120 | jwt({secret: secretCallback}), 121 | function(req, res) { 122 | if (!req.user.admin) return res.send(401); 123 | res.send(200); 124 | }); 125 | ``` 126 | 127 | ### Revoked tokens 128 | It is possible that some tokens will need to be revoked so they cannot be used any longer. You can provide a function as the `isRevoked` option. The signature of the function is `function(req, payload, done)`: 129 | * `req` (`Object`) - The restify `request` object. 130 | * `payload` (`Object`) - An object with the JWT claims. 131 | * `done` (`Function`) - A function with signature `function(err, revoked)` to be invoked once the check to see if the token is revoked or not is complete. 132 | * `err` (`Any`) - The error that occurred. 133 | * `revoked` (`Boolean`) - `true` if the JWT is revoked, `false` otherwise. 134 | 135 | For example, if the `(iss, jti)` claim pair is used to identify a JWT: 136 | ```javascript 137 | var jwt = require('restify-jwt'); 138 | var data = require('./data'); 139 | var utilities = require('./utilities'); 140 | 141 | var isRevokedCallback = function(req, payload, done){ 142 | var issuer = payload.iss; 143 | var tokenId = payload.jti; 144 | 145 | data.getRevokedToken(issuer, tokenId, function(err, token){ 146 | if (err) { return done(err); } 147 | return done(null, !!token); 148 | }); 149 | }; 150 | 151 | app.get('/protected', 152 | jwt({secret: shhhhhhared-secret, 153 | isRevoked: isRevokedCallback}), 154 | function(req, res) { 155 | if (!req.user.admin) return res.send(401); 156 | res.send(200); 157 | }); 158 | ``` 159 | 160 | ### Error handling 161 | 162 | The default behavior is to throw an error when the token is invalid, so you can add your custom logic to manage unauthorized access as follows: 163 | 164 | 165 | ```javascript 166 | app.use(function (err, req, res, next) { 167 | if (err.name === 'UnauthorizedError') { 168 | res.send(401, 'invalid token...'); 169 | } 170 | }); 171 | ``` 172 | 173 | You might want to use this module to identify registered users without preventing unregistered clients to access to some data, you 174 | can do it using the option _credentialsRequired_: 175 | 176 | app.use(jwt({ 177 | secret: 'hello world !', 178 | credentialsRequired: false 179 | })); 180 | 181 | ## Tests 182 | 183 | $ npm install 184 | $ npm test 185 | 186 | ## Credits 187 | 188 | Based on [auth0/express-jwt](https://github.com/auth0/express-jwt). The major difference is that restify-jwt tries to use built in restify errors wherever possible. 189 | 190 | ## License 191 | 192 | [MIT](LICENSE) 193 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var jwt = require('jsonwebtoken'); 2 | var unless = require('express-unless'); 3 | var restify = require('restify'); 4 | var async = require('async'); 5 | 6 | var InvalidCredentialsError = require('restify-errors').InvalidCredentialsError; 7 | 8 | var DEFAULT_REVOKED_FUNCTION = function(_, __, cb) { return cb(null, false); }; 9 | 10 | var getClass = {}.toString; 11 | function isFunction(object) { 12 | return object && getClass.call(object) == '[object Function]'; 13 | } 14 | 15 | function wrapStaticSecretInCallback(secret){ 16 | return function(_, __, cb){ 17 | return cb(null, secret); 18 | }; 19 | } 20 | 21 | module.exports = function(options) { 22 | if (!options || !options.secret) throw new Error('secret should be set'); 23 | 24 | var secretCallback = options.secret; 25 | 26 | if (!isFunction(secretCallback)){ 27 | secretCallback = wrapStaticSecretInCallback(secretCallback); 28 | } 29 | 30 | var isRevokedCallback = options.isRevoked || DEFAULT_REVOKED_FUNCTION; 31 | 32 | var _requestProperty = options.userProperty || options.requestProperty || 'user'; 33 | var credentialsRequired = typeof options.credentialsRequired === 'undefined' ? true : options.credentialsRequired; 34 | 35 | var middleware = function(req, res, next) { 36 | var token; 37 | 38 | if (req.method === 'OPTIONS' && req.headers.hasOwnProperty('access-control-request-headers')) { 39 | var hasAuthInAccessControl = !!~req.headers['access-control-request-headers'] 40 | .split(',').map(function (header) { 41 | return header.trim(); 42 | }).indexOf('authorization'); 43 | 44 | if (hasAuthInAccessControl) { 45 | return next(); 46 | } 47 | } 48 | 49 | if (options.getToken && typeof options.getToken === 'function') { 50 | try { 51 | token = options.getToken(req); 52 | } catch (e) { 53 | return next(e); 54 | } 55 | } else if (req.headers && req.headers.authorization) { 56 | var parts = req.headers.authorization.split(' '); 57 | if (parts.length == 2) { 58 | var scheme = parts[0]; 59 | var credentials = parts[1]; 60 | 61 | if (/^Bearer$/i.test(scheme)) { 62 | token = credentials; 63 | } else { 64 | return next(new InvalidCredentialsError('Format is Authorization: Bearer [token]')); 65 | } 66 | } else { 67 | return next(new InvalidCredentialsError('Format is Authorization: Bearer [token]')); 68 | } 69 | } 70 | 71 | if (!token) { 72 | if (credentialsRequired) { 73 | return next(new InvalidCredentialsError('No authorization token was found')); 74 | } else { 75 | return next(); 76 | } 77 | } 78 | 79 | var dtoken = jwt.decode(token, { complete: true }) || {}; 80 | 81 | async.parallel([ 82 | function(callback){ 83 | var arity = secretCallback.length; 84 | if (arity == 4) { 85 | secretCallback(req, dtoken.header, dtoken.payload, callback); 86 | } else { // arity == 3 87 | secretCallback(req, dtoken.payload, callback); 88 | } 89 | }, 90 | function(callback){ 91 | isRevokedCallback(req, dtoken.payload, callback); 92 | } 93 | ], function(err, results){ 94 | if (err) { return next(err); } 95 | var revoked = results[1]; 96 | if (revoked){ 97 | return next(new restify.UnauthorizedError('The token has been revoked.')); 98 | } 99 | 100 | var secret = results[0]; 101 | 102 | jwt.verify(token, secret, options, function(err, decoded) { 103 | if (err && credentialsRequired) return next(new InvalidCredentialsError(err)); 104 | 105 | req[_requestProperty] = decoded; 106 | next(); 107 | }); 108 | }); 109 | }; 110 | 111 | middleware.unless = unless; 112 | 113 | return middleware; 114 | }; 115 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restify-jwt", 3 | "version": "0.4.0", 4 | "description": "JWT authentication middleware.", 5 | "keywords": [ 6 | "auth", 7 | "authn", 8 | "authentication", 9 | "authz", 10 | "authorization", 11 | "http", 12 | "jwt", 13 | "token", 14 | "oauth", 15 | "restify" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/amrav/restify-jwt.git" 20 | }, 21 | "bugs": { 22 | "url": "http://github.com/amrav/restify-jwt/issues" 23 | }, 24 | "author": { 25 | "name": "Vikrant Varma", 26 | "email": "vikrant.varma94@gmail.com" 27 | }, 28 | "licenses": [ 29 | { 30 | "type": "MIT", 31 | "url": "http://www.opensource.org/licenses/MIT" 32 | } 33 | ], 34 | "main": "./lib", 35 | "dependencies": { 36 | "async": "^0.9.0", 37 | "express-unless": "^0.3.0", 38 | "jsonwebtoken": "^5.0.0" 39 | }, 40 | "devDependencies": { 41 | "mocha": "1.x.x", 42 | "restify": "3.x" 43 | }, 44 | "peerDependencies": { 45 | "restify": "3.x || 4.x", 46 | "restify-errors": "^3.1.0" 47 | }, 48 | "engines": { 49 | "node": ">= 0.10.0" 50 | }, 51 | "scripts": { 52 | "test": "node_modules/.bin/mocha --reporter spec" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/jwt.test.js: -------------------------------------------------------------------------------- 1 | var jwt = require('jsonwebtoken'); 2 | var assert = require('assert'); 3 | 4 | var restifyjwt = require('../lib'); 5 | var restify = require('restify'); 6 | 7 | describe('failure tests', function () { 8 | var req = {}; 9 | var res = {}; 10 | 11 | it('should throw if options not sent', function() { 12 | try { 13 | restifyjwt(); 14 | } catch(e) { 15 | assert.ok(e); 16 | assert.equal(e.message, 'secret should be set'); 17 | } 18 | }); 19 | 20 | it('should throw if no authorization header and credentials are required', function() { 21 | restifyjwt({secret: 'shhhh', credentialsRequired: true})(req, res, function(err) { 22 | assert.ok(err); 23 | assert.equal(err.message, 'No authorization token was found'); 24 | }); 25 | }); 26 | 27 | it('support unless skip', function() { 28 | req.originalUrl = '/index.html'; 29 | restifyjwt({secret: 'shhhh'}).unless({path: '/index.html'})(req, res, function(err) { 30 | assert.ok(!err); 31 | }); 32 | }); 33 | 34 | it('should skip on CORS preflight', function() { 35 | var corsReq = {}; 36 | corsReq.method = 'OPTIONS'; 37 | corsReq.headers = { 38 | 'access-control-request-headers': 'sasa, sras, authorization' 39 | }; 40 | restifyjwt({secret: 'shhhh'})(corsReq, res, function(err) { 41 | assert.ok(!err); 42 | }); 43 | }); 44 | 45 | it('should throw if authorization header is malformed', function() { 46 | req.headers = {}; 47 | req.headers.authorization = 'wrong'; 48 | restifyjwt({secret: 'shhhh'})(req, res, function(err) { 49 | assert.ok(err); 50 | assert.equal(err.message, 'Format is Authorization: Bearer [token]'); 51 | }); 52 | }); 53 | 54 | it('should throw if authorization header is not Bearer', function() { 55 | req.headers = {}; 56 | req.headers.authorization = 'Basic foobar'; 57 | restifyjwt({secret: 'shhhh'})(req, res, function(err) { 58 | assert.ok(err); 59 | assert.equal(err.body.code, 'InvalidCredentials'); 60 | }); 61 | }); 62 | 63 | it('should throw if authorization header is not well-formatted jwt', function() { 64 | req.headers = {}; 65 | req.headers.authorization = 'Bearer wrongjwt'; 66 | restifyjwt({secret: 'shhhh'})(req, res, function(err) { 67 | assert.ok(err); 68 | assert.equal(err.body.code, 'InvalidCredentials'); 69 | }); 70 | }); 71 | 72 | it('should throw if authorization header is not valid jwt', function() { 73 | var secret = 'shhhhhh'; 74 | var token = jwt.sign({foo: 'bar'}, secret); 75 | 76 | req.headers = {}; 77 | req.headers.authorization = 'Bearer ' + token; 78 | restifyjwt({secret: 'different-shhhh'})(req, res, function(err) { 79 | assert.ok(err); 80 | assert.equal(err.body.code, 'InvalidCredentials'); 81 | assert.equal(err.we_cause.message, 'invalid signature'); 82 | }); 83 | }); 84 | 85 | it('should throw if audience is not expected', function() { 86 | var secret = 'shhhhhh'; 87 | var token = jwt.sign({foo: 'bar', aud: 'expected-audience'}, secret); 88 | 89 | req.headers = {}; 90 | req.headers.authorization = 'Bearer ' + token; 91 | restifyjwt({secret: 'shhhhhh', audience: 'not-expected-audience'})(req, res, function(err) { 92 | assert.ok(err); 93 | assert.equal(err.body.code, 'InvalidCredentials'); 94 | assert.equal(err.we_cause.message, 'jwt audience invalid. expected: not-expected-audience'); 95 | }); 96 | }); 97 | 98 | it('should throw if token is expired', function() { 99 | var secret = 'shhhhhh'; 100 | var token = jwt.sign({foo: 'bar', exp: 1382412921 }, secret); 101 | 102 | req.headers = {}; 103 | req.headers.authorization = 'Bearer ' + token; 104 | restifyjwt({secret: 'shhhhhh'})(req, res, function(err) { 105 | assert.ok(err); 106 | assert.equal(err.body.code, 'InvalidCredentials'); 107 | assert.equal(err.we_cause.message, 'jwt expired'); 108 | }); 109 | }); 110 | 111 | it('should throw if token issuer is wrong', function() { 112 | var secret = 'shhhhhh'; 113 | var token = jwt.sign({foo: 'bar', iss: 'http://foo' }, secret); 114 | 115 | req.headers = {}; 116 | req.headers.authorization = 'Bearer ' + token; 117 | restifyjwt({secret: 'shhhhhh', issuer: 'http://wrong'})(req, res, function(err) { 118 | assert.ok(err); 119 | assert.equal(err.body.code, 'InvalidCredentials'); 120 | assert.equal(err.we_cause.message, 'jwt issuer invalid. expected: http://wrong'); 121 | }); 122 | }); 123 | 124 | it('should use errors thrown from custom getToken function', function() { 125 | var secret = 'shhhhhh'; 126 | var token = jwt.sign({foo: 'bar'}, secret); 127 | 128 | function getTokenThatThrowsError() { 129 | throw new restify.errors.InvalidCredentialsError('Invalid token!'); 130 | } 131 | 132 | restifyjwt({ 133 | secret: 'shhhhhh', 134 | getToken: getTokenThatThrowsError 135 | })(req, res, function(err) { 136 | assert.ok(err); 137 | assert.equal(err.message, 'Invalid token!'); 138 | }); 139 | }); 140 | 141 | 142 | it('should throw error when signature is wrong', function() { 143 | var secret = "shhh"; 144 | var token = jwt.sign({foo: 'bar', iss: 'http://www'}, secret); 145 | // manipulate the token 146 | var newContent = new Buffer("{foo: 'bar', edg: 'ar'}").toString('base64'); 147 | var splitetToken = token.split("."); 148 | splitetToken[1] = newContent; 149 | var newToken = splitetToken.join("."); 150 | 151 | // build request 152 | req.headers = []; 153 | req.headers.authorization = 'Bearer ' + newToken; 154 | restifyjwt({secret: secret})(req,res, function(err) { 155 | assert.ok(err); 156 | assert.equal(err.body.code, 'InvalidCredentials'); 157 | assert.equal(err.we_cause.message, 'invalid token'); 158 | }); 159 | }); 160 | 161 | }); 162 | 163 | describe('work tests', function () { 164 | var req = {}; 165 | var res = {}; 166 | 167 | it('should work if authorization header is valid jwt', function() { 168 | var secret = 'shhhhhh'; 169 | var token = jwt.sign({foo: 'bar'}, secret); 170 | 171 | req.headers = {}; 172 | req.headers.authorization = 'Bearer ' + token; 173 | restifyjwt({secret: secret})(req, res, function() { 174 | assert.equal('bar', req.user.foo); 175 | }); 176 | }); 177 | 178 | it('should work if authorization header is valid with a buffer secret', function() { 179 | var secret = new Buffer('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 'base64'); 180 | var token = jwt.sign({foo: 'bar'}, secret); 181 | 182 | req.headers = {}; 183 | req.headers.authorization = 'Bearer ' + token; 184 | restifyjwt({secret: secret})(req, res, function() { 185 | assert.equal('bar', req.user.foo); 186 | }); 187 | }); 188 | 189 | it('should set userProperty if option provided', function() { 190 | var secret = 'shhhhhh'; 191 | var token = jwt.sign({foo: 'bar'}, secret); 192 | 193 | req.headers = {}; 194 | req.headers.authorization = 'Bearer ' + token; 195 | restifyjwt({secret: secret, userProperty: 'auth'})(req, res, function() { 196 | assert.equal('bar', req.auth.foo); 197 | }); 198 | }); 199 | 200 | it('should work if no authorization header and credentials are not required', function() { 201 | req = {}; 202 | restifyjwt({secret: 'shhhh', credentialsRequired: false})(req, res, function(err) { 203 | assert(typeof err === 'undefined'); 204 | }); 205 | }); 206 | 207 | it('should work if token is expired and credentials are not required', function() { 208 | var secret = 'shhhhhh'; 209 | var token = jwt.sign({foo: 'bar', exp: 1382412921}, secret); 210 | 211 | req.headers = {}; 212 | req.headers.authorization = 'Bearer ' + token; 213 | restifyjwt({ secret: secret, credentialsRequired: false })(req, res, function(err) { 214 | assert(typeof err === 'undefined'); 215 | assert(typeof req.user === 'undefined') 216 | }); 217 | }); 218 | 219 | it('should not work if no authorization header', function() { 220 | req = {}; 221 | restifyjwt({ secret: 'shhhh' })(req, res, function(err) { 222 | assert(typeof err !== 'undefined'); 223 | }); 224 | }); 225 | 226 | it('should work with a custom getToken function', function() { 227 | var secret = 'shhhhhh'; 228 | var token = jwt.sign({foo: 'bar'}, secret); 229 | 230 | req.headers = {}; 231 | req.query = {}; 232 | req.query.token = token; 233 | 234 | function getTokenFromQuery(req) { 235 | return req.query.token; 236 | } 237 | 238 | restifyjwt({ 239 | secret: secret, 240 | getToken: getTokenFromQuery 241 | })(req, res, function() { 242 | assert.equal('bar', req.user.foo); 243 | }); 244 | }); 245 | 246 | it('should work with a secretCallback function that accepts header argument', function() { 247 | var secret = 'shhhhhh'; 248 | var secretCallback = function(req, headers, payload, cb) { 249 | assert.equal(headers.alg, 'HS256'); 250 | assert.equal(payload.foo, 'bar'); 251 | process.nextTick(function(){ return cb(null, secret); }); 252 | }; 253 | var token = jwt.sign({foo: 'bar'}, secret); 254 | 255 | req.headers = {}; 256 | req.headers.authorization = 'Bearer ' + token; 257 | restifyjwt({secret: secretCallback})(req, res, function() { 258 | assert.equal('bar', req.user.foo); 259 | }); 260 | }); 261 | }); 262 | -------------------------------------------------------------------------------- /test/multitenancy.test.js: -------------------------------------------------------------------------------- 1 | var jwt = require('jsonwebtoken'); 2 | var assert = require('assert'); 3 | 4 | var restifyjwt = require('../lib'); 5 | var restify = require('restify'); 6 | 7 | describe('multitenancy', function(){ 8 | var req = {}; 9 | var res = {}; 10 | 11 | var tenants = { 12 | 'a': { 13 | secret: 'secret-a' 14 | } 15 | }; 16 | 17 | var secretCallback = function(req, payload, cb){ 18 | var issuer = payload.iss; 19 | if (tenants[issuer]){ 20 | return cb(null, tenants[issuer].secret); 21 | } 22 | 23 | return cb(new restify.errors.UnauthorizedError('Could not find secret for issuer.')); 24 | }; 25 | 26 | var middleware = restifyjwt({ 27 | secret: secretCallback 28 | }); 29 | 30 | it ('should retrieve secret using callback', function(){ 31 | var token = jwt.sign({ iss: 'a', foo: 'bar'}, tenants.a.secret); 32 | 33 | req.headers = {}; 34 | req.headers.authorization = 'Bearer ' + token; 35 | 36 | middleware(req, res, function() { 37 | assert.equal('bar', req.user.foo); 38 | }); 39 | }); 40 | 41 | it ('should throw if an error ocurred when retrieving the token', function(){ 42 | var secret = 'shhhhhh'; 43 | var token = jwt.sign({ iss: 'inexistent', foo: 'bar'}, secret); 44 | 45 | req.headers = {}; 46 | req.headers.authorization = 'Bearer ' + token; 47 | 48 | middleware(req, res, function(err) { 49 | assert.ok(err); 50 | assert.equal(err.body.code, 'UnauthorizedError'); 51 | assert.equal(err.message, 'Could not find secret for issuer.'); 52 | }); 53 | }); 54 | 55 | it ('should fail if token is revoked', function(){ 56 | var token = jwt.sign({ iss: 'a', foo: 'bar'}, tenants.a.secret); 57 | 58 | req.headers = {}; 59 | req.headers.authorization = 'Bearer ' + token; 60 | 61 | var middleware = restifyjwt({ 62 | secret: secretCallback, 63 | isRevoked: function(req, payload, done){ 64 | done(null, true); 65 | } 66 | })(req, res, function(err) { 67 | assert.ok(err); 68 | assert.equal(err.body.code, 'UnauthorizedError'); 69 | assert.equal(err.message, 'The token has been revoked.'); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/revocation.test.js: -------------------------------------------------------------------------------- 1 | var jwt = require('jsonwebtoken'); 2 | var assert = require('assert'); 3 | 4 | var restifyjwt = require('../lib'); 5 | 6 | describe('revoked jwts', function(){ 7 | var secret = 'shhhhhh'; 8 | 9 | var revoked_id = '1234'; 10 | 11 | var middleware = restifyjwt({ 12 | secret: secret, 13 | isRevoked: function(req, payload, done){ 14 | done(null, payload.jti && payload.jti === revoked_id); 15 | } 16 | }); 17 | 18 | it('should throw if token is revoked', function(){ 19 | var req = {}; 20 | var res = {}; 21 | var token = jwt.sign({ jti: revoked_id, foo: 'bar'}, secret); 22 | 23 | req.headers = {}; 24 | req.headers.authorization = 'Bearer ' + token; 25 | 26 | middleware(req, res, function(err) { 27 | assert.ok(err); 28 | assert.equal(err.body.code, 'UnauthorizedError'); 29 | assert.equal(err.message, 'The token has been revoked.'); 30 | }); 31 | }); 32 | 33 | it('should work if token is not revoked', function(){ 34 | var req = {}; 35 | var res = {}; 36 | var token = jwt.sign({ jti: '1233', foo: 'bar'}, secret); 37 | 38 | req.headers = {}; 39 | req.headers.authorization = 'Bearer ' + token; 40 | 41 | middleware(req, res, function() { 42 | assert.equal('bar', req.user.foo); 43 | }); 44 | }); 45 | 46 | it('should throw if error occurs checking if token is revoked', function(){ 47 | var req = {}; 48 | var res = {}; 49 | var token = jwt.sign({ jti: revoked_id, foo: 'bar'}, secret); 50 | 51 | req.headers = {}; 52 | req.headers.authorization = 'Bearer ' + token; 53 | 54 | restifyjwt({ 55 | secret: secret, 56 | isRevoked: function(req, payload, done){ 57 | done(new Error('An error ocurred')); 58 | } 59 | })(req, res, function(err) { 60 | assert.ok(err); 61 | assert.equal(err.message, 'An error ocurred'); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/string_token.test.js: -------------------------------------------------------------------------------- 1 | var jwt = require('jsonwebtoken'); 2 | var assert = require('assert'); 3 | 4 | var restifyjwt = require('../lib'); 5 | 6 | describe('string tokens', function () { 7 | var req = {}; 8 | var res = {}; 9 | 10 | it('should work with a valid string token', function() { 11 | var secret = 'shhhhhh'; 12 | var token = jwt.sign('foo', secret); 13 | 14 | req.headers = {}; 15 | req.headers.authorization = 'Bearer ' + token; 16 | restifyjwt({secret: secret})(req, res, function() { 17 | assert.equal('foo', req.user); 18 | }); 19 | }); 20 | 21 | }); 22 | --------------------------------------------------------------------------------