question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Using fastify-jwt along with Auth0

See original GitHub issue

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 using preValidation: [fastify.authenticate]?

Thanks in advanced!

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:10 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
mcollinacommented, Nov 19, 2019

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.

1reaction
ShogunPandacommented, Nov 11, 2019

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)
})
Read more comments on GitHub >

github_iconTop Results From Across the Web

How to Add Authentication to Your Fastify REST API Using ...
Use Node.js, Fastify, and Auth0 to protect your REST API with OAuth 2.0 ... For access tokens, we're going to use JSON Web...
Read more >
@fastify/jwt - npm
Start using @fastify/jwt in your project by running `npm i @fastify/jwt`. ... HS256 or RS256 JWT tokens, you can use fastify-auth0-verify, ...
Read more >
How to Use Node.js, Fastify and Auth0 to protect REST API
JWT is a standard method for creating JSON-based access tokens. ... Inside the workflow engine, I install fastify-auth0-verify using the standard npm ...
Read more >
Fastify authentication strategy - Daily.dev
The fastify-auth module is a Fastify plugin that provided a utility to handle authentication in routes without adding overhead. It gives us a ......
Read more >
fastify-jwt-webapp - NPM Package Overview - Socket.dev
Being "logged-in" is achieved by passing along the JWT along with each request, as is typical with APIs (via the Authorization header). fastify- ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found