fastify-jwt
Advanced tools
Comparing version 0.1.1 to 0.2.0
104
jwt.js
'use strict' | ||
const fp = require('fastify-plugin') | ||
const JWT = require('jsonwebtoken') | ||
const assert = require('assert') | ||
var fp = require('fastify-plugin') | ||
var JWT = require('jsonwebtoken') | ||
var assert = require('assert') | ||
var steed = require('steed') | ||
function fastifyJWT (fastify, opts, next) { | ||
if (!opts.secret) { | ||
function wrapStaticSecretInCallback (secret) { | ||
return function (request, payload, cb) { | ||
return cb(null, secret) | ||
} | ||
} | ||
function fastifyJwt (fastify, options, next) { | ||
if (!options.secret) { | ||
return next(new Error('missing secret')) | ||
} | ||
const secret = opts.secret | ||
var secret = options.secret | ||
var secretCallback = secret | ||
if (typeof secretCallback !== 'function') { secretCallback = wrapStaticSecretInCallback(secretCallback) } | ||
fastify.decorate('jwt', { | ||
decode: decode, | ||
sign: sign, | ||
verify: verify, | ||
decode: decode, | ||
secret: secret | ||
secret: options.secret | ||
}) | ||
fastify.decorateReply('jwtSign', replySign) | ||
fastify.decorateRequest('jwtVerify', requestVerify) | ||
next() | ||
@@ -59,4 +72,77 @@ | ||
} | ||
function replySign (payload, options, next) { | ||
if (typeof options === 'function') { | ||
next = options | ||
options = {} | ||
} // support no options | ||
var reply = this | ||
if (next === undefined) { | ||
return new Promise(function (resolve, reject) { | ||
reply.jwtSign(payload, options, function (err, val) { | ||
err ? reject(err) : resolve(val) | ||
}) | ||
}) | ||
} | ||
if (!payload) { | ||
return next(new Error('jwtSign requires a payload')) | ||
} | ||
steed.waterfall([ | ||
function getSecret (callback) { | ||
secretCallback(reply.request, payload, callback) | ||
}, | ||
function sign (secret, callback) { | ||
JWT.sign(payload, secret, options, callback) | ||
} | ||
], next) | ||
} // end sign | ||
function requestVerify (options, next) { | ||
if (typeof options === 'function') { | ||
next = options | ||
options = {} | ||
} // support no options | ||
var request = this | ||
if (next === undefined) { | ||
return new Promise(function (resolve, reject) { | ||
request.jwtVerify(options, function (err, val) { | ||
err ? reject(err) : resolve(val) | ||
}) | ||
}) | ||
} | ||
var token | ||
if (request.headers && request.headers.authorization) { | ||
var parts = request.headers.authorization.split(' ') | ||
if (parts.length === 2) { | ||
var scheme = parts[0] | ||
token = parts[1] | ||
if (!/^Bearer$/i.test(scheme)) { | ||
return next(new Error('Format is Authorization: Bearer [token]')) | ||
} | ||
} | ||
} else { | ||
return next(new Error('No Authorization was found in request.headers')) | ||
} | ||
var decodedToken = JWT.decode(token, options) | ||
steed.waterfall([ | ||
function getSecret (callback) { | ||
secretCallback(request, decodedToken, callback) | ||
}, | ||
function verify (secret, callback) { | ||
JWT.verify(token, secret, options, callback) | ||
} | ||
], function (err, result) { | ||
if (err) next(err) | ||
request.user = result | ||
next(null, result) | ||
}) | ||
} // end verify | ||
} | ||
module.exports = fp(fastifyJWT, '>=0.13.1') | ||
module.exports = fp(fastifyJwt, '>= 0.39') |
{ | ||
"name": "fastify-jwt", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"description": "JWT utils for Fastify", | ||
@@ -27,10 +27,13 @@ "main": "jwt.js", | ||
"dependencies": { | ||
"fastify-plugin": "^0.1.1", | ||
"jsonwebtoken": "^8.1.0" | ||
"fastify-plugin": "^0.2.1", | ||
"jsonwebtoken": "^8.1.0", | ||
"steed": "^1.1.3" | ||
}, | ||
"devDependencies": { | ||
"fastify": "^0.30.2", | ||
"fastify": "^0.39", | ||
"request": "^2.83.0", | ||
"request-promise-native": "^1.0.5", | ||
"standard": "^10.0.3", | ||
"tap": "^10.7.2" | ||
"tap": "^11.0.1" | ||
} | ||
} |
120
README.md
@@ -13,9 +13,8 @@ # fastify-jwt | ||
## Usage | ||
Register it as plugin and then access it via `jwt`. | ||
The api is the same of [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken), refer to their documentation to find how use the utilities. | ||
Register as a plugin. This will decorate your `fastify` instance with the standard [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) methods `decode`, `sign`, and `verify`; refer to their documentation to find how to use the utilities. It will also register `request.jwtVerify` and `reply.jwtSign`. You must pass a `secret` when registering the plugin. | ||
```js | ||
const fastify = require('fastify') | ||
fastify.register(require('fastify-jwt'), { secret: 'supersecret' }, err => { | ||
if (err) throw err | ||
fastify.register(require('fastify-jwt'), { | ||
secret: 'supersecret' | ||
}) | ||
@@ -34,2 +33,115 @@ | ||
## API Spec | ||
### fastify-jwt | ||
`fastify-jwt` is a fastify plugin. You must pass a `secret` to the `options` parameter. The `secret` can be a primitive type String or a function that returns a String. Function based `secret` is supported by the `request.jwtVerify()` and `reply.jwtSign()` methods and is called with `request`, `reply`, and `callback` parameters. | ||
#### Example | ||
```js | ||
const fastify = require('fastify')() | ||
const jwt = require('fastify-jwt') | ||
// secret as a string | ||
fastify.register(jwt, { secret: 'supersecret' }) | ||
// secret as a function | ||
fastify.register(jwt, { | ||
secret: function (request, reply, callback) { | ||
// do something | ||
callback(null, 'supersecret') | ||
} | ||
}) | ||
``` | ||
### fastify.jwt.sign(payload [,options] [,callback]) | ||
The `sign` method is an implementation of [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken#jwtsignpayload-secretorprivatekey-options-callback) `.sign()`. Can be used asynchronously by passing a callback function; synchronously without a callback. | ||
### fastify.jwt.verify(token, [,options] [,callback]) | ||
The `verify` method is an implementation of [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback) `.verify()`. Can be used asynchronously by passing a callback function; synchronously without a callback. | ||
#### Example | ||
```js | ||
const token = fastify.jwt.sign({ foo: 'bar' }) | ||
// synchronously | ||
const decoded = fastify.jwt.verify(token) | ||
// asycnhronously | ||
fastify.jwt.verify(token, (err, decoded) => { | ||
if (err) fastify.log.error(err) | ||
fastify.log.info(`Token verified. Foo is ${decoded.foo}`) | ||
}) | ||
``` | ||
### fastify.jwt.decode(token [,options]) | ||
The `decode` method is an implementation of [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken#jwtdecodetoken--options) `.decode()`. Can only be used synchronously. | ||
#### Example | ||
```js | ||
const token = fastify.jwt.sign({ foo: 'bar' }) | ||
const decoded = fastify.jwt.decode(token) | ||
fastify.log.info(`Decoded JWT: ${decoded}`) | ||
``` | ||
### fastify.jwt.secret | ||
For your convenience, the `secret` you specify during `.register` is made available via `fastify.jwt.secret`. `request.jwtVerify()` and `reply.jwtSign()` will wrap non-function secrets in a callback function. `request.jwtVerify()` and `reply.jwtSign()` use an asynchronous waterfall method to retrieve your secret. It's recommended that your use these methods if your `secret` method is asynchronous. | ||
### reply.jwtSign(payload, [options,] callback) | ||
### request.jwtVerify([options,] callback) | ||
These methods are very similar to their standard jsonwebtoken counterparts. | ||
#### Example | ||
```js | ||
const fastify = require('fastify')() | ||
fastify.register(jwt, { | ||
secret: function (request, reply, callback) { | ||
// do something | ||
callback(null, 'supersecret') | ||
} | ||
}) | ||
fastify.post('/sign', function (request, reply) { | ||
reply.jwtSign(request.body.payload, function (err, token) { | ||
return reply.send(err || { 'token': token }) | ||
}) | ||
}) | ||
fastify.get('/verify', function (request, reply) { | ||
request.jwtVerify(function (err, decoded) { | ||
return reply.send(err || decoded) | ||
}) | ||
}) | ||
fastify.listen(3000, function (err) { | ||
if (err) fastify.log.error(err) | ||
fastify.log.info(`Server live on port: ${fastify.server.address().port}`) | ||
// sign payload and get JWT | ||
request({ | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: { | ||
payload: { | ||
foo: 'bar' | ||
} | ||
}, | ||
uri: `http://localhost:${fastify.server.address().port}/sign`, | ||
json: true | ||
}, function (err, response, body) { | ||
if (err) fastify.log.error(err) | ||
fastify.log.info(`JWT token is ${body}`) | ||
// verify JWT | ||
request({ | ||
method: 'GET', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
authorization: 'Bearer ' + sign.token | ||
}, | ||
uri: 'http://localhost:' + fastify.server.address().port + '/verify', | ||
json: true | ||
}, function (err, response, body) { | ||
if (err) fastify.log.error(err) | ||
fastify.log.info(`JWT verified. Foo is ${body.bar}`) | ||
}) | ||
}) | ||
}) | ||
``` | ||
## Acknowledgements | ||
@@ -36,0 +148,0 @@ |
331
test.js
'use strict' | ||
const test = require('tap').test | ||
const Fastify = require('fastify') | ||
const jwt = require('./jwt') | ||
var test = require('tap').test | ||
var Fastify = require('fastify') | ||
var rp = require('request-promise-native') | ||
var jwt = require('./jwt') | ||
test('fastify-jwt should expose jwt methods', t => { | ||
t.plan(5) | ||
const fastify = Fastify() | ||
test('fastify-jwt should expose jwt methods', function (t) { | ||
t.plan(8) | ||
var fastify = Fastify() | ||
fastify | ||
.register(jwt, { secret: 'supersecret' }, t.error) | ||
.after(() => { | ||
.register(jwt, { secret: 'supersecret' }) | ||
.ready(function () { | ||
t.ok(fastify.jwt.decode) | ||
t.ok(fastify.jwt.sign) | ||
t.ok(fastify.jwt.verify) | ||
t.ok(fastify.jwt.decode) | ||
t.ok(fastify.jwt.secret) | ||
}) | ||
fastify.get('/test', function (request, reply) { | ||
t.ok(request.jwtVerify) | ||
t.ok(reply.jwtSign) | ||
reply.send({ foo: 'bar' }) | ||
}) | ||
fastify.listen(0, function (err) { | ||
fastify.server.unref() | ||
t.error(err) | ||
rp({ | ||
method: 'GET', | ||
uri: 'http://localhost:' + fastify.server.address().port + '/test', | ||
json: true | ||
}).then(function (response) { | ||
t.ok(response) | ||
}).catch(function (err) { | ||
t.fail(err) | ||
}) | ||
}) | ||
}) | ||
test('jwt.secret should be the same as the one given as option', t => { | ||
t.plan(2) | ||
const fastify = Fastify() | ||
test('fastify-jwt fails without secret', function (t) { | ||
t.plan(1) | ||
var fastify = Fastify() | ||
fastify | ||
.register(jwt, { secret: 'supersecret' }, t.error) | ||
.after(() => { | ||
t.is(fastify.jwt.secret, 'supersecret') | ||
.register(jwt) | ||
.listen(0, function (err) { | ||
t.is(err.message, 'missing secret') | ||
}) | ||
}) | ||
test('sync sign and verify', t => { | ||
t.plan(3) | ||
const fastify = Fastify() | ||
fastify | ||
.register(jwt, { secret: 'supersecret' }, t.error) | ||
.after(() => { | ||
const token = fastify.jwt.sign({ hello: 'world' }) | ||
t.ok(token) | ||
t.equal(fastify.jwt.verify(token).hello, 'world') | ||
test('sign and verify', function (t) { | ||
t.plan(8) | ||
var fastify = Fastify() | ||
fastify.register(jwt, { secret: 'supersecret' }) | ||
fastify.post('/signCallback', function (request, reply) { | ||
reply.jwtSign(request.body.payload, function (err, token) { | ||
return reply.send(err || { 'token': token }) | ||
}) | ||
}) | ||
}) | ||
test('async sign and verify', t => { | ||
t.plan(5) | ||
const fastify = Fastify() | ||
fastify | ||
.register(jwt, { secret: 'supersecret' }, t.error) | ||
.after(() => { | ||
fastify.jwt.sign({ hello: 'world' }, (err, token) => { | ||
fastify.get('/verifyCallback', function (request, reply) { | ||
request.jwtVerify(function (err, decoded) { | ||
return reply.send(err || decoded) | ||
}) | ||
}) | ||
fastify.post('/signPromise', function (request, reply) { | ||
reply.jwtSign(request.body.payload).then( | ||
function (token) { | ||
return reply.send({ 'token': token }) | ||
}).catch(function (err) { | ||
return reply.send(err) | ||
}) | ||
}) | ||
fastify.get('/verifyPromise', function (request, reply) { | ||
request.jwtVerify().then(function (decoded) { | ||
return reply.send(decoded) | ||
}).catch(function (err) { | ||
return reply.send(err) | ||
}) | ||
}) | ||
fastify.listen(0, function (err) { | ||
fastify.server.unref() | ||
t.error(err) | ||
}) | ||
t.test('syncronously', function (t) { | ||
t.plan(1) | ||
fastify.ready(function () { | ||
var token = fastify.jwt.sign({ foo: 'bar' }) | ||
var decoded = fastify.jwt.verify(token) | ||
t.is(decoded.foo, 'bar') | ||
}) | ||
}) | ||
t.test('asynchronously', function (t) { | ||
t.plan(5) | ||
fastify.ready(function () { | ||
fastify.jwt.sign({ foo: 'bar' }, function (err, token) { | ||
t.error(err) | ||
t.ok(token) | ||
fastify.jwt.verify(token, (err, payload) => { | ||
fastify.jwt.verify(token, function (err, decoded) { | ||
t.error(err) | ||
t.equal(payload.hello, 'world') | ||
t.ok(decoded) | ||
t.is(decoded.foo, 'bar') | ||
}) | ||
}) | ||
}) | ||
}) | ||
t.test('jwtSign and jwtVerify with callbacks', function (t) { | ||
t.plan(2) | ||
rp({ | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: { | ||
payload: { | ||
foo: 'bar' | ||
} | ||
}, | ||
uri: 'http://localhost:' + fastify.server.address().port + '/signCallback', | ||
json: true | ||
}).then(function (sign) { | ||
rp({ | ||
method: 'GET', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
authorization: 'Bearer ' + sign.token | ||
}, | ||
uri: 'http://localhost:' + fastify.server.address().port + '/verifyCallback', | ||
json: true | ||
}).then(function (verify) { | ||
t.ok(verify) | ||
t.is(verify.foo, 'bar') | ||
}).catch(function (err) { | ||
t.fail(err.message) | ||
}) | ||
}).catch(function (err) { | ||
t.fail(err.message) | ||
}) | ||
}) | ||
t.test('jwtSign and jwtVerify without callbacks', function (t) { | ||
t.plan(2) | ||
rp({ | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: { | ||
payload: { | ||
foo: 'bar' | ||
} | ||
}, | ||
uri: 'http://localhost:' + fastify.server.address().port + '/signPromise', | ||
json: true | ||
}).then(function (sign) { | ||
rp({ | ||
method: 'GET', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
authorization: 'Bearer ' + sign.token | ||
}, | ||
uri: 'http://localhost:' + fastify.server.address().port + '/verifyPromise', | ||
json: true | ||
}).then(function (verify) { | ||
t.ok(verify) | ||
t.is(verify.foo, 'bar') | ||
}).catch(function (err) { | ||
t.fail(err.message) | ||
}) | ||
}).catch(function (err) { | ||
t.fail(err.message) | ||
}) | ||
}) | ||
t.test('jwtVerify throws No Authorization error', function (t) { | ||
t.plan(1) | ||
rp({ | ||
method: 'GET', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
uri: 'http://localhost:' + fastify.server.address().port + '/verifyCallback', | ||
json: true | ||
}).then(function () { | ||
t.fail() | ||
}).catch(function (err) { | ||
t.is(err.error.message, 'No Authorization was found in request.headers') | ||
}) | ||
}) | ||
t.test('jwtVerify throws Authorization Format error', function (t) { | ||
t.plan(1) | ||
rp({ | ||
method: 'GET', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
authorization: 'Invalid TokenFormat' | ||
}, | ||
uri: 'http://localhost:' + fastify.server.address().port + '/verifyCallback', | ||
json: true | ||
}).then(function () { | ||
t.fail() | ||
}).catch(function (err) { | ||
t.is(err.error.message, 'Format is Authorization: Bearer [token]') | ||
}) | ||
}) | ||
t.test('jwtSign throws payload error', function (t) { | ||
t.plan(1) | ||
rp({ | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: JSON.stringify({ | ||
notAPayload: 'sorry' | ||
}), | ||
uri: 'http://localhost:' + fastify.server.address().port + '/signCallback', | ||
json: true | ||
}).then(function () { | ||
t.fail() | ||
}).catch(function (err) { | ||
t.is(err.error.message, 'jwtSign requires a payload') | ||
}) | ||
}) | ||
}) | ||
test('should throw if no secret is given as option', t => { | ||
test('decode', function (t) { | ||
t.plan(1) | ||
const fastify = Fastify() | ||
fastify.register(jwt, err => { | ||
t.is(err.message, 'missing secret') | ||
var fastify = Fastify() | ||
fastify.register(jwt, { secret: 'shh' }).ready(function () { | ||
var token = fastify.jwt.sign({ foo: 'bar' }) | ||
var decoded = fastify.jwt.decode(token) | ||
t.is(decoded.foo, 'bar') | ||
}) | ||
}) | ||
test('sign should throw if the payload is missing', t => { | ||
t.plan(2) | ||
const fastify = Fastify() | ||
test('secret as a function', function (t) { | ||
t.plan(4) | ||
var fastify = Fastify() | ||
fastify | ||
.register(jwt, { secret: 'supersecret' }, t.error) | ||
.after(() => { | ||
try { | ||
fastify.jwt.sign() | ||
t.fail() | ||
} catch (err) { | ||
t.is(err.message, 'missing payload') | ||
.register(jwt, { | ||
secret: function (request, reply, callback) { | ||
callback(null, 'supersecret') | ||
} | ||
}) | ||
}) | ||
test('verify should throw if the token is missing', t => { | ||
t.plan(2) | ||
const fastify = Fastify() | ||
fastify | ||
.register(jwt, { secret: 'supersecret' }, t.error) | ||
.after(() => { | ||
try { | ||
fastify.jwt.verify() | ||
t.fail() | ||
} catch (err) { | ||
t.is(err.message, 'missing token') | ||
} | ||
fastify.post('/sign', function (request, reply) { | ||
reply.jwtSign(request.body.payload, function (err, token) { | ||
if (err) { return reply.send(err) } | ||
return reply.send({token}) | ||
}) | ||
}) | ||
fastify.get('/verify', function (request, reply) { | ||
request.jwtVerify(function (err, decoded) { | ||
if (err) { return reply.send(err) } | ||
return reply.send(decoded) | ||
}) | ||
}) | ||
fastify.listen(0, function (err) { | ||
fastify.server.unref() | ||
t.error(err) | ||
rp({ | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: { | ||
payload: { | ||
foo: 'bar' | ||
} | ||
}, | ||
uri: 'http://localhost:' + fastify.server.address().port + '/sign', | ||
json: true | ||
}).then(function (sign) { | ||
t.ok(sign) | ||
rp({ | ||
method: 'GET', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
authorization: 'Bearer ' + sign.token | ||
}, | ||
uri: 'http://localhost:' + fastify.server.address().port + '/verify', | ||
json: true | ||
}).then(function (verify) { | ||
t.ok(verify) | ||
t.is(verify.foo, 'bar') | ||
}).catch(function (err) { | ||
t.fail(err) | ||
}) | ||
}).catch(function (err) { | ||
t.fail(err) | ||
}) | ||
}) | ||
}) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
18715
406
153
3
5
6
1
+ Addedsteed@^1.1.3
+ Addedfastfall@1.5.1(transitive)
+ Addedfastify-plugin@0.2.2(transitive)
+ Addedfastparallel@2.4.1(transitive)
+ Addedfastq@1.17.1(transitive)
+ Addedfastseries@1.7.2(transitive)
+ Addedreusify@1.0.4(transitive)
+ Addedsteed@1.1.3(transitive)
+ Addedxtend@4.0.2(transitive)
- Removedfastify-plugin@0.1.1(transitive)
Updatedfastify-plugin@^0.2.1