UNPKG

@fastify/jwt

Version:
1,828 lines (1,496 loc) 83.5 kB
'use strict' const { test } = require('node:test') const Fastify = require('fastify') const { createSigner } = require('fast-jwt') const jwt = require('../jwt') const defaultExport = require('../jwt').default const { fastifyJwt: namedExport } = require('../jwt') const helper = require('./helper') const passphrase = 'super secret passphrase' const { privateKey: privateKeyProtected, publicKey: publicKeyProtected } = helper.generateKeyPairProtected(passphrase) const { privateKey: privateKeyProtectedECDSA, publicKey: publicKeyProtectedECDSA } = helper.generateKeyPairECDSAProtected(passphrase) const { privateKey, publicKey } = helper.generateKeyPair() const { privateKey: privateKeyECDSA, publicKey: publicKeyECDSA } = helper.generateKeyPairECDSA() test('export', async function (t) { t.plan(3) await t.test('module export', function (t) { t.plan(1) t.assert.strictEqual(typeof jwt, 'function') }) await t.test('default export', function (t) { t.plan(1) t.assert.strictEqual(typeof defaultExport, 'function') }) await t.test('named export', function (t) { t.plan(1) t.assert.strictEqual(typeof namedExport, 'function') }) }) test('register', async function (t) { t.plan(17) await t.test('Expose jwt methods', async function (t) { t.plan(8) const fastify = Fastify() fastify.register(jwt, { secret: 'test', cookie: { cookieName: 'token', signed: false } }) fastify.get('/methods', function (request, reply) { t.assert.ok(request.jwtDecode) t.assert.ok(request.jwtVerify) t.assert.ok(reply.jwtSign) return {} }) await fastify.ready() t.assert.ok(fastify.jwt.decode) t.assert.ok(fastify.jwt.options) t.assert.ok(fastify.jwt.sign) t.assert.ok(fastify.jwt.verify) t.assert.ok(fastify.jwt.cookie) return fastify.inject({ method: 'get', url: '/methods' }) }) await t.test('secret as an object', async function (t) { const fastify = Fastify() await fastify.register(jwt, { secret: { private: privateKey, public: publicKey } }).ready() }) await t.test('secret as a Buffer', async function (t) { const fastify = Fastify() await fastify.register(jwt, { secret: Buffer.from('some secret', 'base64') }).ready() }) await t.test('secret as a function with a callback returning a Buffer', async function (t) { const fastify = Fastify() await fastify.register(jwt, { secret: (_request, _token, callback) => { callback(null, Buffer.from('some secret', 'base64')) } }).ready() }) await t.test('secret as a function returning a promise with Buffer', async function (t) { const fastify = Fastify() await fastify.register(jwt, { secret: () => Promise.resolve(Buffer.from('some secret', 'base64')) }).ready() }) await t.test('secret as an async function returning a Buffer', async function (t) { const fastify = Fastify() await fastify.register(jwt, { secret: async () => Buffer.from('some secret', 'base64') }).ready() }) await t.test('deprecated use of options prefix', async function (t) { t.plan(1) const fastify = Fastify() await t.assert.rejects(() => fastify.register(jwt, { secret: { private: privateKey, public: publicKey }, options: { algorithme: 'RS256' } }).ready(), undefined, 'options prefix is deprecated') }) await t.test('secret as a malformed object', async function (t) { t.plan(2) await t.test('only private key (Must return an error)', async function (t) { t.plan(1) const fastify = Fastify() await t.assert.rejects(() => fastify.register(jwt, { secret: { private: privateKey }, sign: { algorithm: 'RS256', aud: 'Some audience', iss: 'Some issuer', sub: 'Some subject' } }).ready(), undefined, 'missing public key') }) await t.test('only public key (Must not return an error)', async function (t) { const fastify = Fastify() await fastify.register(jwt, { secret: { public: publicKey }, sign: { algorithm: 'ES256', aud: 'Some audience', iss: 'Some issuer', sub: 'Some subject' } }).ready() }) }) await t.test('decode, sign and verify global options (with default HS algorithm)', async function (t) { const fastify = Fastify() await fastify.register(jwt, { secret: 'test', decode: { complete: true }, sign: { iss: 'Some issuer', sub: 'Some subject', aud: 'Some audience' }, verify: { allowedIss: 'Some issuer', allowedSub: 'Some subject', allowedAud: 'Some audience' } }).ready() }) await t.test('decode, sign and verify global options and secret as an object', async function (t) { t.plan(2) await t.test('RS algorithm signed certificates', async function (t) { const fastify = Fastify() await fastify.register(jwt, { secret: { private: privateKey, public: publicKey }, decode: { complete: true }, sign: { algorithm: 'RS256', aud: 'Some audience', iss: 'Some issuer', sub: 'Some subject' }, verify: { algorithms: ['RS256'], allowedAud: 'Some audience', allowedIss: 'Some issuer', allowedSub: 'Some subject' } }).ready() }) await t.test('ES algorithm signed certificates', async function (t) { const fastify = Fastify() await fastify.register(jwt, { secret: { private: privateKeyECDSA, public: publicKeyECDSA }, decode: { complete: true }, sign: { algorithm: 'ES256', aud: 'Some audience', iss: 'Some issuer', sub: 'Some subject' }, verify: { algorithms: ['ES256'], allowedAud: 'Some audience', allowedIss: 'Some issuer', allowedSub: 'Some subject' } }).ready() }) }) async function runWithSecret (t, secret) { const fastify = Fastify() fastify.register(jwt, { secret }) fastify.post('/sign', async function (request, reply) { const token = await reply.jwtSign(request.body) return reply.send({ token }) }) fastify.get('/verify', function (request) { return request.jwtVerify() }) await fastify.ready() const signResponse = await fastify.inject({ method: 'post', url: '/sign', payload: { foo: 'bar' } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verify', headers: { authorization: `Bearer ${token}` } }) const decodedToken = JSON.parse(verifyResponse.payload) t.assert.strictEqual(decodedToken.foo, 'bar') } await t.test('secret as a function with callback', t => { return runWithSecret(t, function (_request, _token, callback) { callback(null, 'some-secret') }) }) await t.test('secret as a function returning a promise', t => { return runWithSecret(t, function () { return Promise.resolve('some-secret') }) }) await t.test('secret as an async function', t => { return runWithSecret(t, async function () { return 'some-secret' }) }) await t.test('secret as a function with callback returning a Buffer', t => { return runWithSecret(t, function (_request, _token, callback) { callback(null, Buffer.from('some-secret', 'base64')) }) }) await t.test('secret as a function returning a promise with a Buffer', t => { return runWithSecret(t, function () { return Promise.resolve(Buffer.from('some secret', 'base64')) }) }) await t.test('secret as an async function returning a Buffer', t => { return runWithSecret(t, async function () { return Buffer.from('some secret', 'base64') }) }) await t.test('fail without secret', async function (t) { const fastify = Fastify() await t.assert.rejects(() => fastify .register(jwt) .ready(), undefined, 'missing secret') }) }) test('sign and verify with HS-secret', async function (t) { t.plan(2) await t.test('server methods', async function (t) { t.plan(2) const fastify = Fastify() fastify.register(jwt, { secret: 'test' }) await fastify.ready() await t.test('synchronous', function (t) { t.plan(1) const token = fastify.jwt.sign({ foo: 'bar' }) const decoded = fastify.jwt.verify(token) t.assert.strictEqual(decoded.foo, 'bar') }) await t.test('with callbacks', function (t) { t.plan(3) const { promise, resolve } = helper.withResolvers() fastify.jwt.sign({ foo: 'bar' }, function (error, token) { t.assert.ifError(error) fastify.jwt.verify(token, function (error, decoded) { t.assert.ifError(error) t.assert.strictEqual(decoded.foo, 'bar') resolve() }) }) return promise }) }) await t.test('route methods', async function (t) { t.plan(2) const fastify = Fastify() fastify.register(jwt, { secret: 'test' }) fastify.post('/signSync', function (request, reply) { return reply.jwtSign(request.body).then(function (token) { return { token } }) }) fastify.get('/verifySync', function (request) { return request.jwtVerify() }) fastify.post('/signAsync', function (request, reply) { reply.jwtSign(request.body, function (error, token) { return reply.send(error || { token }) }) }) fastify.get('/verifyAsync', function (request, reply) { request.jwtVerify(function (error, decodedToken) { return reply.send(error || decodedToken) }) }) await fastify.ready() await t.test('synchronous', async function (t) { t.plan(2) const signResponse = await fastify.inject({ method: 'post', url: '/signSync', payload: { foo: 'bar' } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifySync', headers: { authorization: `Bearer ${token}` } }) const decodedToken = JSON.parse(verifyResponse.payload) t.assert.strictEqual(decodedToken.foo, 'bar') }) await t.test('with callbacks', async function (t) { t.plan(2) const signResponse = await fastify.inject({ method: 'post', url: '/signAsync', payload: { foo: 'bar' } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifyAsync', headers: { authorization: `Bearer ${token}` } }) const decodedToken = JSON.parse(verifyResponse.payload) t.assert.strictEqual(decodedToken.foo, 'bar') }) }) }) test('sign and verify with RSA/ECDSA certificates and global options', async function (t) { t.plan(5) await t.test('RSA certificates', async function (t) { t.plan(2) const config = { secret: { private: privateKey, public: publicKey }, sign: { algorithm: 'RS256', iss: 'test' }, verify: { algorithms: ['RS256'], allowedIss: 'test' } } await t.test('server methods', async function (t) { t.plan(2) let signedToken await t.test('signer mode', async function (t) { t.plan(2) const fastifySigner = Fastify() fastifySigner.register(jwt, config) await fastifySigner.ready() await t.test('synchronous', function (t) { t.plan(2) signedToken = fastifySigner.jwt.sign({ foo: 'bar' }) const decoded = fastifySigner.jwt.verify(signedToken) t.assert.strictEqual(decoded.foo, 'bar') t.assert.strictEqual(decoded.iss, 'test') }) await t.test('with callbacks', function (t) { t.plan(4) const { promise, resolve } = helper.withResolvers() fastifySigner.jwt.sign({ foo: 'bar' }, function (error, token) { t.assert.ifError(error) fastifySigner.jwt.verify(token, function (error, decoded) { t.assert.ifError(error) t.assert.strictEqual(decoded.foo, 'bar') t.assert.strictEqual(decoded.iss, 'test') resolve() }) }) return promise }) }) await t.test('verifier mode', async function (t) { t.plan(2) const fastifyVerifier = Fastify() fastifyVerifier.register(jwt, { ...config, secret: { public: config.secret.public // no private key } }) await fastifyVerifier.ready() await t.test('synchronous', function (t) { t.plan(3) try { fastifyVerifier.jwt.sign({ foo: 'baz' }) } catch (error) { t.assert.strictEqual(error.message, 'unable to sign: secret is configured in verify mode') } const decoded = fastifyVerifier.jwt.verify(signedToken) t.assert.strictEqual(decoded.foo, 'bar') t.assert.strictEqual(decoded.iss, 'test') }) await t.test('with callbacks', function (t) { t.plan(4) try { fastifyVerifier.jwt.sign({ foo: 'baz' }, function (error) { // as for now, verifier-only error is not propagated here t.assert.ifError('SHOULD NOT BE HERE') t.assert.ifError(error) }) } catch (error) { t.assert.strictEqual(error.message, 'unable to sign: secret is configured in verify mode') } const { promise, resolve } = helper.withResolvers() fastifyVerifier.jwt.verify( signedToken, function (error, decoded) { t.assert.ifError(error) t.assert.strictEqual(decoded.foo, 'bar') t.assert.strictEqual(decoded.iss, 'test') resolve() } ) return promise }) }) }) await t.test('route methods', async function (t) { t.plan(2) let signedToken await t.test('signer mode', async function (t) { t.plan(2) const fastify = Fastify() fastify.register(jwt, config) fastify.post('/signSync', function (request, reply) { reply.jwtSign(request.body) .then(function (token) { return reply.send({ token }) }) }) fastify.get('/verifySync', function (request) { return request.jwtVerify() }) fastify.post('/signAsync', function (request, reply) { reply.jwtSign(request.body, function (error, token) { return reply.send(error || { token }) }) }) fastify.get('/verifyAsync', function (request, reply) { request.jwtVerify(function (error, decodedToken) { return reply.send(error || decodedToken) }) }) await fastify.ready() await t.test('synchronous', async function (t) { t.plan(3) const signResponse = await fastify.inject({ method: 'post', url: '/signSync', payload: { foo: 'bar' } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) signedToken = token const verifyResponse = await fastify.inject({ method: 'get', url: '/verifySync', headers: { authorization: `Bearer ${token}` } }) const decodedToken = JSON.parse(verifyResponse.payload) t.assert.strictEqual(decodedToken.foo, 'bar') t.assert.strictEqual(decodedToken.iss, 'test') }) await t.test('with callbacks', async function (t) { t.plan(3) const signResponse = await fastify.inject({ method: 'post', url: '/signAsync', payload: { foo: 'bar' } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifyAsync', headers: { authorization: `Bearer ${token}` } }) const decodedToken = JSON.parse(verifyResponse.payload) t.assert.strictEqual(decodedToken.foo, 'bar') t.assert.strictEqual(decodedToken.iss, 'test') }) }) await t.test('verifier mode', async function (t) { t.plan(1) const fastifyVerifier = Fastify() fastifyVerifier.register(jwt, { ...config, secret: { public: config.secret.public // no private key } }) fastifyVerifier.post('/signSync', function (request, reply) { reply.jwtSign(request.body) .then(function (token) { return reply.send({ token }) }) }) fastifyVerifier.get('/verifySync', function (request) { return request.jwtVerify() }) await fastifyVerifier.ready() await t.test('synchronous verifier', async function (t) { t.plan(4) const response = await fastifyVerifier.inject({ method: 'post', url: '/signSync', payload: { foo: 'bar' } }) t.assert.strictEqual(response.statusCode, 500) const payload = JSON.parse(response.payload) t.assert.strictEqual(payload.message, 'unable to sign: secret is configured in verify mode') const verifyResponse = await fastifyVerifier.inject({ method: 'get', url: '/verifySync', headers: { authorization: `Bearer ${signedToken}` } }) const decodedToken = JSON.parse(verifyResponse.payload) t.assert.strictEqual(decodedToken.foo, 'bar') t.assert.strictEqual(decodedToken.iss, 'test') }) }) }) }) await t.test('ECDSA certificates', async function (t) { t.plan(2) await t.test('server methods', async function (t) { t.plan(2) const fastify = Fastify() fastify.register(jwt, { secret: { private: privateKeyECDSA, public: publicKeyECDSA }, sign: { algorithm: 'ES256', sub: 'test' }, verify: { algorithms: ['ES256'], allowedSub: 'test' } }) await fastify.ready() await t.test('synchronous', function (t) { t.plan(2) const token = fastify.jwt.sign({ foo: 'bar' }) const decoded = fastify.jwt.verify(token) t.assert.strictEqual(decoded.foo, 'bar') t.assert.strictEqual(decoded.sub, 'test') }) await t.test('with callbacks', function (t) { t.plan(4) const { promise, resolve } = helper.withResolvers() fastify.jwt.sign({ foo: 'bar' }, function (error, token) { t.assert.ifError(error) fastify.jwt.verify(token, function (error, decoded) { t.assert.ifError(error) t.assert.strictEqual(decoded.foo, 'bar') t.assert.strictEqual(decoded.sub, 'test') resolve() }) }) return promise }) }) await t.test('route methods', async function (t) { t.plan(2) const fastify = Fastify() fastify.register(jwt, { secret: { private: privateKeyECDSA, public: publicKeyECDSA }, sign: { algorithm: 'ES256', sub: 'test' }, verify: { allowedSub: 'test' } }) fastify.post('/signSync', function (request, reply) { reply.jwtSign(request.body) .then(function (token) { return reply.send({ token }) }) }) fastify.get('/verifySync', function (request) { return request.jwtVerify() }) fastify.post('/signAsync', function (request, reply) { reply.jwtSign(request.body, function (error, token) { return reply.send(error || { token }) }) }) fastify.get('/verifyAsync', function (request, reply) { request.jwtVerify(function (error, decodedToken) { return reply.send(error || decodedToken) }) }) await fastify.ready() await t.test('synchronous', async function (t) { t.plan(3) const signResponse = await fastify.inject({ method: 'post', url: '/signSync', payload: { foo: 'bar' } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifySync', headers: { authorization: `Bearer ${token}` } }) const decodedToken = JSON.parse(verifyResponse.payload) t.assert.strictEqual(decodedToken.foo, 'bar') t.assert.strictEqual(decodedToken.sub, 'test') }) await t.test('with callbacks', async function (t) { t.plan(3) const signResponse = await fastify.inject({ method: 'post', url: '/signAsync', payload: { foo: 'bar' } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifyAsync', headers: { authorization: `Bearer ${token}` } }) const decodedToken = JSON.parse(verifyResponse.payload) t.assert.strictEqual(decodedToken.foo, 'bar') t.assert.strictEqual(decodedToken.sub, 'test') }) }) }) await t.test('RSA certificates (passphrase protected)', async function (t) { t.plan(2) await t.test('server methods', async function (t) { t.plan(2) const fastify = Fastify() fastify.register(jwt, { secret: { private: { key: privateKeyProtected, passphrase: 'super secret passphrase' }, public: publicKeyProtected }, sign: { algorithm: 'RS256', aud: 'test' }, verify: { allowedAud: 'test' } }) await fastify.ready() await t.test('synchronous', function (t) { t.plan(2) const token = fastify.jwt.sign({ foo: 'bar' }) const decoded = fastify.jwt.verify(token) t.assert.strictEqual(decoded.aud, 'test') t.assert.strictEqual(decoded.foo, 'bar') }) await t.test('with callbacks', function (t) { t.plan(4) const { promise, resolve } = helper.withResolvers() fastify.jwt.sign({ foo: 'bar' }, function (error, token) { t.assert.ifError(error) fastify.jwt.verify(token, function (error, decoded) { t.assert.ifError(error) t.assert.strictEqual(decoded.aud, 'test') t.assert.strictEqual(decoded.foo, 'bar') resolve() }) }) return promise }) }) await t.test('route methods', async function (t) { t.plan(2) const fastify = Fastify() fastify.register(jwt, { secret: { private: { key: privateKeyProtected, passphrase: 'super secret passphrase' }, public: publicKeyProtected }, sign: { algorithm: 'RS256', aud: 'test' }, verify: { allowedAud: 'test' } }) fastify.post('/signSync', function (request, reply) { reply.jwtSign(request.body) .then(function (token) { return reply.send({ token }) }) }) fastify.get('/verifySync', function (request) { return request.jwtVerify() }) fastify.post('/signAsync', function (request, reply) { reply.jwtSign(request.body, function (error, token) { return reply.send(error || { token }) }) }) fastify.get('/verifyAsync', function (request, reply) { request.jwtVerify(function (error, decodedToken) { return reply.send(error || decodedToken) }) }) await fastify.ready() await t.test('synchronous', async function (t) { t.plan(3) const signResponse = await fastify.inject({ method: 'post', url: '/signSync', payload: { foo: 'bar' } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifySync', headers: { authorization: `Bearer ${token}` } }) const decodedToken = JSON.parse(verifyResponse.payload) t.assert.strictEqual(decodedToken.aud, 'test') t.assert.strictEqual(decodedToken.foo, 'bar') }) await t.test('with callbacks', async function (t) { t.plan(3) const signResponse = await fastify.inject({ method: 'post', url: '/signAsync', payload: { foo: 'bar' } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifyAsync', headers: { authorization: `Bearer ${token}` } }) const decodedToken = JSON.parse(verifyResponse.payload) t.assert.strictEqual(decodedToken.aud, 'test') t.assert.strictEqual(decodedToken.foo, 'bar') }) }) }) await t.test('ECDSA certificates (passphrase protected)', async function (t) { t.plan(2) await t.test('server methods', async function (t) { t.plan(2) const fastify = Fastify() fastify.register(jwt, { secret: { private: { key: privateKeyProtectedECDSA, passphrase: 'super secret passphrase' }, public: publicKeyProtectedECDSA }, sign: { algorithm: 'ES256', sub: 'test' }, verify: { allowedSub: 'test' } }) await fastify.ready() await t.test('synchronous', function (t) { t.plan(2) const token = fastify.jwt.sign({ foo: 'bar' }) const decoded = fastify.jwt.verify(token) t.assert.strictEqual(decoded.foo, 'bar') t.assert.strictEqual(decoded.sub, 'test') }) await t.test('with callbacks', function (t) { t.plan(4) const { promise, resolve } = helper.withResolvers() fastify.jwt.sign({ foo: 'bar' }, function (error, token) { t.assert.ifError(error) fastify.jwt.verify(token, function (error, decoded) { t.assert.ifError(error) t.assert.strictEqual(decoded.foo, 'bar') t.assert.strictEqual(decoded.sub, 'test') resolve() }) }) return promise }) }) await t.test('route methods', async function (t) { t.plan(2) const fastify = Fastify() fastify.register(jwt, { secret: { private: { key: privateKeyProtectedECDSA, passphrase: 'super secret passphrase' }, public: publicKeyProtectedECDSA }, sign: { algorithm: 'ES256', sub: 'test' }, verify: { allowedSub: 'test' } }) fastify.post('/signSync', function (request, reply) { reply.jwtSign(request.body) .then(function (token) { return reply.send({ token }) }) }) fastify.get('/verifySync', function (request) { return request.jwtVerify() }) fastify.post('/signAsync', function (request, reply) { reply.jwtSign(request.body, function (error, token) { return reply.send(error || { token }) }) }) fastify.get('/verifyAsync', function (request, reply) { request.jwtVerify(function (error, decodedToken) { return reply.send(error || decodedToken) }) }) await fastify.ready() await t.test('synchronous', async function (t) { t.plan(3) const signResponse = await fastify.inject({ method: 'post', url: '/signSync', payload: { foo: 'bar' } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifySync', headers: { authorization: `Bearer ${token}` } }) const decodedToken = JSON.parse(verifyResponse.payload) t.assert.strictEqual(decodedToken.foo, 'bar') t.assert.strictEqual(decodedToken.sub, 'test') }) await t.test('with callbacks', async function (t) { t.plan(3) const signResponse = await fastify.inject({ method: 'post', url: '/signAsync', payload: { foo: 'bar' } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifyAsync', headers: { authorization: `Bearer ${token}` } }) const decodedToken = JSON.parse(verifyResponse.payload) t.assert.strictEqual(decodedToken.foo, 'bar') t.assert.strictEqual(decodedToken.sub, 'test') }) }) }) await t.test('Overriding global options', async function (t) { t.plan(2) await t.test('server methods', async function (t) { t.plan(2) const fastify = Fastify() fastify.register(jwt, { secret: { private: privateKey, public: publicKey }, sign: { algorithm: 'RS256', iss: 'test' }, verify: { algorithms: ['RS256'], allowedIss: 'test' } }) await fastify.ready() await t.test('synchronous', function (t) { t.plan(2) const localOptions = Object.assign({}, fastify.jwt.options.sign) localOptions.iss = 'other' const token = fastify.jwt.sign({ foo: 'bar' }, localOptions) const decoded = fastify.jwt.verify(token, { iss: 'other' }) t.assert.strictEqual(decoded.foo, 'bar') t.assert.strictEqual(decoded.iss, 'other') }) await t.test('with callbacks', function (t) { t.plan(4) const localOptions = Object.assign({}, fastify.jwt.options.sign) localOptions.iss = 'other' const { promise, resolve } = helper.withResolvers() fastify.jwt.sign({ foo: 'bar' }, localOptions, function (error, token) { t.assert.ifError(error) fastify.jwt.verify(token, { iss: 'other' }, function (error, decoded) { t.assert.ifError(error) t.assert.strictEqual(decoded.foo, 'bar') t.assert.strictEqual(decoded.iss, 'other') resolve() }) }) return promise }) }) await t.test('route methods', async function (t) { t.plan(2) const fastify = Fastify() fastify.register(jwt, { secret: { private: privateKey, public: publicKey }, sign: { algorithm: 'RS256', iss: 'test' }, verify: { allowedIss: 'test' } }) fastify.post('/signSync', function (request, reply) { reply.jwtSign(request.body) .then(function (token) { return reply.send({ token }) }) }) fastify.get('/verifySync', function (request) { return request.jwtVerify() }) fastify.post('/signAsync', function (request, reply) { reply.jwtSign(request.body, function (error, token) { return reply.send(error || { token }) }) }) fastify.get('/verifyAsync', function (request, reply) { request.jwtVerify(function (error, decodedToken) { return reply.send(error || decodedToken) }) }) await fastify.ready() await t.test('synchronous', async function (t) { t.plan(3) const signResponse = await fastify.inject({ method: 'post', url: '/signSync', payload: { foo: 'bar' } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifySync', headers: { authorization: `Bearer ${token}` } }) const decodedToken = JSON.parse(verifyResponse.payload) t.assert.strictEqual(decodedToken.foo, 'bar') t.assert.strictEqual(decodedToken.iss, 'test') }) await t.test('with callbacks', async function (t) { t.plan(3) const signResponse = await fastify.inject({ method: 'post', url: '/signAsync', payload: { foo: 'bar' } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifyAsync', headers: { authorization: `Bearer ${token}` } }) const decodedToken = JSON.parse(verifyResponse.payload) t.assert.strictEqual(decodedToken.foo, 'bar') t.assert.strictEqual(decodedToken.iss, 'test') }) }) }) }) test('sign and verify with trusted token', async function (t) { t.plan(2) await t.test('Trusted token verification', async function (t) { t.plan(2) const f = Fastify() f.register(jwt, { secret: 'test', trusted: (_request, { jti }) => jti !== 'untrusted' }) f.get('/', (request, reply) => { request.jwtVerify() .then(function (decodedToken) { delete decodedToken?.iat t.assert.deepStrictEqual(decodedToken, { foo: 'bar', jti: 'trusted' }) return reply.send(decodedToken) }) .catch(function (error) { return reply.send(error) }) }) const signer = createSigner({ key: 'test', jti: 'trusted' }) const trustedToken = signer({ foo: 'bar' }) const response = await f.inject({ method: 'get', url: '/', headers: { authorization: `Bearer ${trustedToken}` } }) t.assert.strictEqual(response.statusCode, 200) }) await t.test('Trusted token - async verification', async function (t) { t.plan(2) const f = Fastify() f.register(jwt, { secret: 'test', trusted: (_request, { jti }) => Promise.resolve(jti !== 'untrusted') }) f.get('/', (request, reply) => { request.jwtVerify() .then(function (decodedToken) { delete decodedToken?.iat t.assert.deepStrictEqual(decodedToken, { foo: 'bar', jti: 'trusted' }) return reply.send(decodedToken) }) .catch(function (error) { return reply.send(error) }) }) const signer = createSigner({ key: 'test', jti: 'trusted' }) const trustedToken = signer({ foo: 'bar' }) const response = await f.inject({ method: 'get', url: '/', headers: { authorization: `Bearer ${trustedToken}` } }) t.assert.strictEqual(response.statusCode, 200) }) }) test('decode', async function (t) { t.plan(2) await t.test('without global options', async function (t) { t.plan(2) await t.test('without local options', async function (t) { t.plan(1) const fastify = Fastify() fastify.register(jwt, { secret: 'test' }) await fastify.ready() const token = fastify.jwt.sign({ foo: 'bar' }) const decoded = fastify.jwt.decode(token) t.assert.strictEqual(decoded.foo, 'bar') }) await t.test('with local options', async function (t) { t.plan(3) const fastify = Fastify() fastify.register(jwt, { secret: 'test' }) await fastify.ready() const token = fastify.jwt.sign({ foo: 'bar' }) const decoded = fastify.jwt.decode(token, { complete: true }) t.assert.strictEqual(decoded.header.alg, 'HS256') t.assert.strictEqual(decoded.header.typ, 'JWT') t.assert.strictEqual(decoded.payload.foo, 'bar') }) }) await t.test('with global options', async function (t) { t.plan(2) await t.test('without overriding global options', async function (t) { t.plan(3) const fastify = Fastify() fastify.register(jwt, { secret: 'test', decode: { complete: true } }) await fastify.ready() const token = fastify.jwt.sign({ foo: 'bar' }) const decoded = fastify.jwt.decode(token) t.assert.strictEqual(decoded.header.alg, 'HS256') t.assert.strictEqual(decoded.header.typ, 'JWT') t.assert.strictEqual(decoded.payload.foo, 'bar') }) await t.test('overriding global options', async function (t) { t.plan(4) const fastify = Fastify() fastify.register(jwt, { secret: 'test', decode: { complete: true } }) await fastify.ready() const token = fastify.jwt.sign({ foo: 'bar' }) const decoded = fastify.jwt.decode(token, { complete: false }) t.assert.strictEqual(decoded.header, undefined) t.assert.strictEqual(decoded.payload, undefined) t.assert.strictEqual(decoded.signature, undefined) t.assert.strictEqual(decoded.foo, 'bar') }) }) }) test('errors', async function (t) { t.plan(16) const fastify = Fastify() fastify.register(jwt, { secret: 'test', trusted: (_request, { jti }) => jti !== 'untrusted', decode: { checkTyp: 'JWT' } }) fastify.post('/sign', function (request, reply) { reply.jwtSign(request.body.payload, { sign: { iss: 'foo' } }) .then(function (token) { return reply.send({ token }) }) .catch(function (error) { return reply.send(error) }) }) fastify.get('/verify', function (request, reply) { request.jwtVerify() .then(function (decodedToken) { return reply.send(decodedToken) }) .catch(function (error) { return reply.send(error) }) }) fastify.get('/verifyFailOnIss', function (request, reply) { request.jwtVerify({ verify: { allowedIss: 'bar' } }) .then(function (decodedToken) { return reply.send(decodedToken) }) .catch(function (error) { return reply.send(error) }) }) fastify.get('/verifyFailMissingRequiredClaim', function (request, reply) { request.jwtVerify({ verify: { requiredClaims: ['bar'] } }) .then(function (decodedToken) { return reply.send(decodedToken) }) .catch(function (error) { return reply.send(error) }) }) fastify.get('/verifyFailOnAlgorithmMismatch', function (request, reply) { request.jwtVerify({ verify: { algorithms: ['invalid'] } }) .then(function (decodedToken) { return reply.send(decodedToken) }) .catch(function (error) { return reply.send(error) }) }) fastify.get('/verifyFailOnInvalidClockTimestamp', function (request, reply) { request.jwtVerify({ verify: { clockTimestamp: 'not_a_number' } }) .then(function (decodedToken) { return reply.send(decodedToken) }) .catch(function (error) { return reply.send(error) }) }) fastify.get('/verifyErrorCallbackCount', function (request, reply) { let count = 0 request.jwtVerify({ verify: { key: 'invalid key' } }, function () { count += 1 setImmediate(function () { reply.send({ count }) }) }) }) fastify.get('/verifyFailUntrustedToken', function (request, reply) { request.jwtVerify() .then(function (decodedToken) { return reply.send(decodedToken) }) .catch(function (error) { return reply.send(error) }) }) fastify.get('/verifyFailUnsignedToken', function (request, reply) { request.jwtVerify() .then(function (decodedToken) { return reply.send(decodedToken) }) .catch(function (error) { return reply.send(error) }) }) await fastify.ready() await t.test('no payload error', async function (t) { t.plan(1) const response = await fastify.inject({ method: 'post', url: '/sign', payload: { payload: null } }) const error = JSON.parse(response.payload) t.assert.strictEqual(error.message, 'jwtSign requires a payload') }) await t.test('no authorization header error', async function (t) { t.plan(2) const response = await fastify.inject({ method: 'get', url: '/verify' }) const error = JSON.parse(response.payload) t.assert.strictEqual(error.message, 'No Authorization was found in request.headers') t.assert.strictEqual(response.statusCode, 401) }) await t.test('no bearer authorization header error', async function (t) { t.plan(2) const response = await fastify.inject({ method: 'get', url: '/verify', headers: { authorization: 'Invalid Format' } }) const error = JSON.parse(response.payload) t.assert.strictEqual(error.message, 'No Authorization was found in request.headers') t.assert.strictEqual(response.statusCode, 401) }) await t.test('authorization header malformed error', async function (t) { t.plan(2) const response = await fastify .inject({ method: 'get', url: '/verify', headers: { authorization: 'Bearer 1.2.3' } }) const error = JSON.parse(response.payload) t.assert.strictEqual(response.statusCode, 401) t.assert.strictEqual(error.message, 'Authorization token is invalid: The token header is not a valid base64url serialized JSON.') }) await t.test('authorization header invalid type error', async function (t) { t.plan(2) const response = await fastify .inject({ method: 'get', url: '/verify', headers: { authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUiJ9.e30.ha5mKb-6aDOVHh5lRaUBNdDmMAYLOl1no3LQkV2mAMQ' } }) const error = JSON.parse(response.payload) t.assert.strictEqual(response.statusCode, 401) t.assert.strictEqual(error.message, 'Authorization token is invalid: The type must be "JWT".') }) await t.test('Bearer authorization format error', async function (t) { t.plan(2) const response = await fastify.inject({ method: 'get', url: '/verify', headers: { authorization: 'Bearer Bad Format' } }) const error = JSON.parse(response.payload) t.assert.strictEqual(error.message, 'Format is Authorization: Bearer [token]') t.assert.strictEqual(response.statusCode, 400) }) await t.test('Expired token error', async function (t) { t.plan(2) const expiredToken = fastify.jwt.sign({ exp: Math.floor(Date.now() / 1000) - 60, foo: 'bar' }) const response = await fastify.inject({ method: 'get', url: '/verify', headers: { authorization: `Bearer ${expiredToken}` } }) const error = JSON.parse(response.payload) t.assert.strictEqual(error.message, 'Authorization token expired') t.assert.strictEqual(response.statusCode, 401) }) await t.test('Invalid signature error', async function (t) { t.plan(2) const signer = createSigner({ key: Buffer.alloc(64) }) const invalidSignatureToken = signer({ foo: 'bar' }) const response = await fastify.inject({ method: 'get', url: '/verify', headers: { authorization: `Bearer ${invalidSignatureToken}` } }) const error = JSON.parse(response.payload) t.assert.strictEqual(error.message, 'Authorization token is invalid: The token signature is invalid.') t.assert.strictEqual(response.statusCode, 401) }) await t.test('Untrusted token error', async function (t) { t.plan(2) const signer = createSigner({ key: 'test', jti: 'untrusted' }) const untrustedToken = signer({ foo: 'bar' }) const response = await fastify.inject({ method: 'get', url: '/verifyFailUntrustedToken', headers: { authorization: `Bearer ${untrustedToken}` } }) const error = JSON.parse(response.payload) t.assert.strictEqual(error.message, 'Untrusted authorization token') t.assert.strictEqual(response.statusCode, 401) }) await t.test('Untrusted token error - async verification', async function (t) { t.plan(2) const f = Fastify() f.register(jwt, { secret: 'test', trusted: (_request, { jti }) => Promise.resolve(jti !== 'untrusted') }) f.get('/', (request, reply) => { request.jwtVerify() .then(function (decodedToken) { return reply.send(decodedToken) }) .catch(function (error) { return reply.send(error) }) }) const signer = createSigner({ key: 'test', jti: 'untrusted' }) const untrustedToken = signer({ foo: 'bar' }) const response = await f.inject({ method: 'get', url: '/', headers: { authorization: `Bearer ${untrustedToken}` } }) const error = JSON.parse(response.payload) t.assert.strictEqual(error.message, 'Untrusted authorization token') t.assert.strictEqual(response.statusCode, 401) }) await t.test('Unsigned token error', async function (t) { t.plan(2) const signer = createSigner({ algorithm: 'none' }) const unsignedToken = signer({ foo: 'bar' }) const response = await fastify.inject({ method: 'get', url: '/verifyFailUnsignedToken', headers: { authorization: `Bearer ${unsignedToken}` } }) const error = JSON.parse(response.payload) t.assert.strictEqual(error.message, 'Unsigned authorization token') t.assert.strictEqual(response.statusCode, 401) }) await t.test('requestVerify function: steed.waterfall error function loop test', async function (t) { t.plan(3) const signResponse = await fastify.inject({ method: 'post', url: '/sign', payload: { payload: { foo: 'bar' } } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifyFailOnIss', headers: { authorization: `Bearer ${token}` } }) const error = JSON.parse(verifyResponse.payload) t.assert.strictEqual(error.message, 'Authorization token is invalid: The iss claim value is not allowed.') t.assert.strictEqual(verifyResponse.statusCode, 401) }) await t.test('requestVerify function: wrap missing required claims', async function (t) { t.plan(3) const signResponse = await fastify.inject({ method: 'post', url: '/sign', payload: { payload: { foo: 'bar' } } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifyFailMissingRequiredClaim', headers: { authorization: `Bearer ${token}` } }) const error = JSON.parse(verifyResponse.payload) t.assert.strictEqual(error.message, 'Authorization token is invalid: The bar claim is required.') t.assert.strictEqual(verifyResponse.statusCode, 401) }) await t.test('requestVerify function: algorithm mismatch error', async function (t) { t.plan(3) const signResponse = await fastify.inject({ method: 'post', url: '/sign', payload: { payload: { foo: 'bar' } } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifyFailOnAlgorithmMismatch', headers: { authorization: `Bearer ${token}` } }) const error = JSON.parse(verifyResponse.payload) t.assert.strictEqual(error.message, 'Authorization token is invalid: Invalid public key provided for algorithms invalid.') t.assert.strictEqual(verifyResponse.statusCode, 401) }) await t.test('requestVerify function: invalid timestamp', async function (t) { t.plan(3) const signResponse = await fastify.inject({ method: 'post', url: '/sign', payload: { payload: { foo: 'bar' } } }) const token = JSON.parse(signResponse.payload).token t.assert.ok(token) const verifyResponse = await fastify.inject({ method: 'get', url: '/verifyFailOnInvalidClockTimestamp', headers: { authorization: `Bearer ${token}` } }) const error = JSON.parse(verifyResponse.payload) t.assert.strictEqual(error.message