Closed mattduffield closed 4 years ago
Thanks for reaching out. Would you like to send a PR with that example? We are happy to review!
I could not figure out how to do it with fastify-jwt
and ended up writing my own in-app plugin:
'use strict';
const jwksFactory = require('jwks-rsa');
const jwt = require('jsonwebtoken');
module.exports = function jwtPlugin(instance, opts, done) {
if (!process.env.JWKS_URL) {
instance.decorateRequest('jwtVerify', () => {
throw Error('missing env var JWKS_URL');
});
return done();
}
const jwks = jwksFactory({
cache: true,
rateLimit: true,
jwksUri: process.env.JWKS_URL
});
instance.decorateRequest('jwtVerify', (token, options) => {
return new Promise(promise);
function promise(resolve, reject) {
verifyWrapper(token, jwks, options, (err, decoded) => {
if (err) {
return reject(err);
}
resolve(decoded);
});
}
});
done();
};
module.exports[Symbol.for('skip-override')] = true;
function verifyWrapper(token, jwks, options, cb) {
jwt.verify(token, getKey, options, (err, decoded) => {
if (err) {
return cb(err);
}
cb(null, decoded);
});
function getKey(header, callback) {
jwks.getSigningKey(header.kid, (err, key) => {
if (err) {
return callback(err);
}
callback(null, key.publicKey || key.rsaPublicKey);
});
}
}
Hi @jsumners, thanks for the plugin. Can you show how you are using it in an endpoint?
Thanks @jsumners, I found a way to do it exactly like fastify-jwt with an extra package. I wanted the same signature so that it was consistent.
I’m reopening this, as it should be easy to setup.
Thanks, I was going to ask about that. I think having it consolidated would be really awesome!
I have a working example with validates Auth0 issued tokens, both HS256 and RS256 using fastify-jwt.
The dependencies are:
{
"dependencies": {
"auth0": "^2.20.0",
"boom": "^7.3.0",
"dotenv": "^8.2.0",
"fastify": "^2.10.0",
"fastify-jwt": "^1.2.0",
"got": "^9.6.0",
"http-status-codes": "^1.4.0"
}
}
It uses environment variable for configuration, here's the required ones:
AUTH0_DOMAIN=YOURDOMAIN.auth0.com # Can be omitted if you're verifying HS256 tokens and you're providing AUTH0_AUDIENCE. If AUTH0_AUDIENCE. it should match the audience of the tokens
AUTH0_AUDIENCE=YOUR_AUTH0_API_URL # Can be omitted. Anyway should match the audience of the tokens
AUTH0_CLIENT_SECRET=... # Can be omitted. Only needed when verifying HS256 tokens
And here's the code:
require('dotenv/config')
const { AuthenticationClient } = require('auth0')
const { badData, forbidden, internal, notFound, unauthorized } = require('boom')
const fastify = require('fastify')
const fastifyJwt = require('fastify-jwt')
const { FORBIDDEN, INTERNAL_SERVER_ERROR, NOT_FOUND, OK, UNAUTHORIZED } = require('http-status-codes')
const got = require('got')
function handleErrors(error, _r, reply) {
let boom = error
if (!boom.isBoom) {
// Serialize the error stack
const cwd = process.cwd()
let stack = []
if (boom.stack) {
stack = boom.stack
.split('\n')
.slice(1)
.map(s =>
s
.trim()
.replace(/^at /, '')
.replace(cwd, '$ROOT')
)
}
// Message must be passed as data otherwise Boom will hide it
boom = internal('', { message: `[${boom.code || boom.name}] ${boom.message}`, stack })
}
reply
.code(boom.output.statusCode)
.type('application/json')
.headers(boom.output.headers)
.send({ ...boom.output.payload, ...boom.data })
}
function handleNotFoundError(_r, reply) {
reply.code(NOT_FOUND).send(notFound('Not found.'))
}
async function getSecret(request, reply, cb) {
try {
const { header } = request.jwtDecode()
// If the algorithm is not using RS256, the encryption key is Auth0 client secret
if (header.alg.startsWith('HS')) {
return cb(null, process.env.AUTH0_CLIENT_SECRET)
}
// Hit the well-known URL in order to get the key
const response = await got(`${request.auth0Domain}.well-known/jwks.json`, { json: true })
// Find the key with ID and algorithm matching the JWT token header
const key = response.body.keys.find(k => k.alg == header.alg && k.kid === header.kid)
if (!key) {
throw new Error('No matching key found in the set.')
}
// certToPEM extracted from https://github.com/auth0/node-jwks-rsa/blob/master/src/utils.js
cb(null, '-----BEGIN CERTIFICATE-----\n@\n-----END CERTIFICATE-----\n'.replace('@', key.x5c[0]))
} catch (e) {
let message = e.response
? `Unable to get the JWS: [HTTP ${e.response.statusCode}] ${JSON.stringify(e)}`
: `Unable to get the JWS: ${e.message}`
cb(internal('', { message }))
}
}
async function start() {
const server = fastify({ logger: true })
try {
// Error handling
server.setErrorHandler(handleErrors)
server.setNotFoundHandler(handleNotFoundError)
// Normalize the domain in order to get a good URL for JWKS
const domain = new URL(`https://${process.env.AUTH0_DOMAIN || 'localhost'}`).toString()
// Setup Fastify-JWT
server.register(fastifyJwt, {
secret: getSecret,
verify: {
aud: process.env.AUTH0_AUDIENCE || domain,
issuer: domain,
algorithms: ['HS256', 'RS256']
}
})
server.decorateRequest('auth0Domain', domain)
server.decorateRequest('jwtDecode', function() {
if (!this.headers && !this.headers.authorization) {
throw unauthorized('Missing Authorization HTTP header.')
} else if (!this.headers.authorization.match(/^Bearer\s+/)) {
throw badData('Authorization header should be in format: Bearer [token].')
}
return server.jwt.decode(this.headers.authorization.split(/\s+/)[1], { complete: true })
})
server.decorate('authenticate', async function(request, reply) {
try {
await request.jwtVerify({ complete: true })
} catch (e) {
if (e.isBoom) {
throw e
} else if (e.message == 'No Authorization was found in request.headers') {
throw unauthorized('Missing Authorization HTTP header.')
}
throw forbidden(e.message)
}
})
server.route({
method: 'GET',
url: '/verify',
schema: {
response: {
[OK]: {
type: 'object',
properties: {
token: { type: 'object', additionalProperties: true }
},
additionalProperties: false,
required: ['token']
},
[UNAUTHORIZED]: {
type: 'object',
properties: {
statusCode: { type: 'number', enum: [UNAUTHORIZED] },
error: { type: 'string', enum: ['Unauthorized'] },
message: { type: 'string', pattern: '.+' }
},
required: ['statusCode', 'error', 'message'],
additionalProperties: false
},
[FORBIDDEN]: {
type: 'object',
properties: {
statusCode: { type: 'number', enum: [FORBIDDEN] },
error: { type: 'string', enum: ['Forbidden'] },
message: { type: 'string', pattern: '.+' }
},
required: ['statusCode', 'error', 'message'],
additionalProperties: false
},
[INTERNAL_SERVER_ERROR]: {
type: 'object',
properties: {
statusCode: {
type: 'number',
enum: [INTERNAL_SERVER_ERROR]
},
error: {
type: 'string',
enum: ['Internal Server Error']
},
message: { type: 'string', pattern: '.+' },
stack: { type: 'array', items: { type: 'string' } }
},
required: ['statusCode', 'error', 'message'],
additionalProperties: false
}
}
},
handler(request, reply) {
reply.send({ token: request.user })
},
preValidation: server.authenticate
})
await server.listen(parseInt(process.env.PORT || '3000', 10), '0.0.0.0')
} catch (err) {
server.log.error(err)
process.exit(1)
}
}
start().catch(e => {
console.error(e)
process.exit(1)
})
Wow! we should think about how to add some feature in this plugin to use it more flawlessly.
Some consideration on code:
require('auth0')
is unused${request.auth0Domain}.well-known/jwks.json
be cached?We are working on a new module at NearForm to automate some of this! Il 19 nov 2019, 23:01 +0100, Manuel Spigolon notifications@github.com, ha scritto:
Wow! we should think about how to add some feature in this plugin to use it more flawlessly. Some consideration on code:
• require('auth0') is unused • could ${request.auth0Domain}.well-known/jwks.json be cached? • instead of (deprecated) boom you could use fastify-sensible
— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub, or unsubscribe.
Hello,
I have used fastify-jwt in the past where my servers were in charge of issuing JWT tokens, etc. However, I am now trying to use Auth0 as the authority. I want to use Fastify as my server and verify tokens sent from clients and devices that have previously authenticate using Auth0 directly.
My question/request is how is this achieved using
fastify-jwt
? I have seen several other node packages but all based on the premise of using Express. I don't want to use Express and want to have everything working the "Fastify" way. Would it be possible to provide a working repo that uses RS256 and allow us to mark up our endpoints usingpreValidation: [fastify.authenticate]
?Thanks in advanced!