Authentication is an absolute necessity in any application exposed to users. We need to make sure that the user trying to access our application is an actual user. In other words, we have to make sure that the user’s identity is verifiable. JWT or JSON Web Tokens allow us to perform user authentication. But how do we setup a Fastify JWT application?

JWT Fastify integration is fully supported by means of the fastify-jwt package. This package provides a bunch of JWT utilities. Using this package, we can build a very basic authentication mechanism using JWT.

In this post, we will look at step-by-step approach to setup a JWT-based authentication to protect Fastify REST endpoints.

1 – Installation of the Fastify JWT Packages

First step is to install the necessary packages for creating a Fastify application to use JWT authentication.

$ mkdir fastify-jwt-demo
$ npm init -y
$ npm install fastify fastify-plugin fastify-autoload fastify-jwt dotenv

As you can see, we are creating a new project and installing a bunch of packages. Let’s understand the role of each package.

  • The fastify package is the core of the framework
  • This is followed by fastify-plugin which is a plugin helper
  • Next, we have fastify-autoload that helps load the plugins to the Fastify context based on folder path
  • Moving on, we have the fastify-jwt package that brings in the necessary JWT related utilities
  • Finally, we are also installing the dotenv package to support environment variables in our Node application.

To run our application, we will add a dev script to our package.json file.

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "fastify start -w -l info -P app.js"
},

Once all the packages are installed, we can move on with the next step.

2 – Fastify JWT Key Management

To generate a JWT, we need to have a secret key. It can be a primitive string, a function that returns a string or an object. The point is that it should be handled securely by your application.

While there are many advanced approaches to secure keys, the simplest is to use some sort of an environment file with the secret key. At the time of starting the application, we load the secret key from the environment variable. This approach makes sure that we don’t hard-code our secret key in the application code.

To use environment variables in our application, we have installed the dotenv package. Now, we create a file named .env in the root directory of our project. Within the file, we have a variable SECRET_KEY with some random value. The dotenv package makes sure that the environment variables within the .env file are available as part of the process.env object.

SECRET_KEY=supersecretkey

Important note here is that we should never commit the .env file to a source code repository such as Github. This is to protect the secret key or any other credentials from being accessible to everyone who has access to the Github repo.

Now, we need to use the key value. To do so, we will create a special method for loading the SECRET_KEY from the environment file.

To better structure our application, we create separate directory config with a file named configuration.js. See below:

function loadEnvironmentVariable(keyname) {
    const envVar = process.env[keyname];

    if (!envVar) {
        throw new Error(`Configuration must include ${keyname}`)
    }

    return envVar;
}

module.exports = {
    secretKey: loadEnvironmentVariable('SECRET_KEY'),
}

We export the secret key from this particular file. However, we also make sure that we validate the existence of the SECRET_KEY property within the .env file. In case the property is not present, we throw an error and stop the application from even starting up.

3 – Fastify JWT Verify Configuration Plugin

It is time to setup the fastify-jwt package and configure it to use our secret key. Basically, here we need to create a Fastify JWT Verify utility.

We will create a plugins directory in the root of our project. Inside the directory, we will create a file jwt.js.

const fp = require('fastify-plugin');
const configuration = require('../config/configuration')

module.exports = fp(function (fastify, opts, done) {

    fastify.register(require('fastify-jwt'), {
        secret: configuration.secretKey
    })

    fastify.decorate("authenticate", async function (request, reply) {
        try {
            await request.jwtVerify()
        } catch (err) {
            reply.send(err)
        }
    })

    done()
})

This is a typical Fastify plugin. We use the Fastify register() API to register the fastify-jwt plugin. It takes a configuration object with the secretKey as input.

Also, we decorate the Fastify context with a utility function authenticate. This function calls the jwtVerify() method on the request object. Basically, the jwtVerify() method verifies the JWT for our Fastify application. We will be using this utility in the next section.

To expose these functionalities in the parent context, we use fastify-plugin and wrap the JWT plugin within the fp function. This will ensure that we can use the authenticate() utility elsewhere in our application. Basically, it will make sure that JWT specific methods from the fastify-jwt package such as jwtSign() are available in our application context.

4 – Implementing the Fastify API Endpoints

Now it is time to implement the Fastify API routes. We will create a folder named routes in the root of our projects and create a file root.js.

For the purpose of this example, we will create a signup endpoint. This endpoint will issue the JWT token. Typically, in applications this is the place where we match the user id and password combination and then issue the token. Assuming that the user id and password is correct, we will simply issue the token.

'use strict'

module.exports = async function (fastify, opts) {
    fastify.post('/signup', (req, reply) => {
        const token = fastify.jwt.sign({ "username": "Adam Smith" })
        reply.send({ token })
    })
}

To create the token we use the fastify.jwt.sign() method. This method takes an object as input. Ideally, this should be the user object. We return the token using reply.send method.

Now, we create another endpoint /protected. Basically, this endpoint can only be accessed with a valid token. We use the onRequest hook available with the route to call the fastify.authenticate utility we added to the JWT plugin in the previous section. The authenticate utility will call the jwtVerify() method to verify the token and authenticate the user. Here’s the complete code for the request handlers.

'use strict'

module.exports = async function (fastify, opts) {
    fastify.post('/signup', (req, reply) => {
        const token = fastify.jwt.sign({ "username": "Adam Smith" })
        reply.send({ token })
    })

    fastify.get(
        "/protected",
        {
            onRequest: [fastify.authenticate]
        },
        async function (request, reply) {
            return request.user
        }
    )
}

Lastly, we need to load the plugins and routes to our Fastify context. In the root of our project, we create a file app.js where we use fastify-autoload to load all plugins and routes from the respective directories.

'use strict'

const path = require('path')
const AutoLoad = require('fastify-autoload')

module.exports = async function (fastify, opts) {
  // This loads all plugins defined in plugins
  // those should be support plugins that are reused
  // through your application
  fastify.register(AutoLoad, {
    dir: path.join(__dirname, 'plugins'),
    options: Object.assign({}, opts)
  })

  // This loads all plugins defined in routes
  // define your routes in one of these
  fastify.register(AutoLoad, {
    dir: path.join(__dirname, 'routes'),
    options: Object.assign({}, opts)
  })
}

5 – Testing the Fastify JWT Authentication

We can now test the application. First make a call to /signup to obtain the JWT token.

$ curl -X POST localhost:3000/signup
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkFkYW0gU21pdGgiLCJpYXQiOjE2NTA4NTM1ODV9.0_d3gziSXM1qrtGBpSIVyA7S4K6b5v6fw-nJS24lZ_4"}

The response object contains the token. Then, we make a call to the /protected endpoint with the token as part of Authorization header to get the data.

$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkFkYW0gU21pdGgiLCJpYXQiOjE2NTA4NTM1ODV9.0_d3gziSXM1qrtGBpSIVyA7S4K6b5v6fw-nJS24lZ_4" localhost:3000/protected
{"username":"Adam Smith","iat":1650853585}%   

If we make a request to the /protected endpoint without the token, we get a HTTP status code of 401 or Unauthorized.

$ curl localhost:3000/protected
{"statusCode":401,"error":"Unauthorized","message":"No Authorization was found in request.headers"}% 

Conclusion

Fastify JWT implementation using the fastify-jwt package is just the first step towards building a robust authentication mechanism. It allows us to quickly setup our application to support JWT-based authentication.

The code for this post is available on Github.

Going forward, we would look at more advanced authentication and authorization setups with Fastify.

Want to learn more about Fastify? Check out this post on creating a Fastify REST API with Postgres.

If you have any comments or queries about this post, please feel free to mention them in the comments section below.

Categories: BlogFastify

Saurabh Dashora

Saurabh is a Software Architect with over 12 years of experience. He has worked on large-scale distributed systems across various domains and organizations. He is also a passionate Technical Writer and loves sharing knowledge in the community.

4 Comments

Nickolas · July 20, 2022 at 7:43 pm

Thank you for this perfectly written article on how to use jwt authentication with Fastify. There are so many articles out there there are either outdated or just poorly written. You should setup a BuyMeACoffee profile so that us users can reward you if we want to.

Anonymous · March 28, 2023 at 8:43 pm

Thank You!
Really helped me solve a silly bug what was causing a huge issue.

    Saurabh Dashora · April 4, 2023 at 5:51 am

    Glad that the article helped!

Leave a Reply

Your email address will not be published. Required fields are marked *