@fastify/jwt
Version:
JWT utils for Fastify
1,828 lines (1,496 loc) • 83.5 kB
JavaScript
'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