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.
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.
Saurabh Dashora · July 21, 2022 at 1:51 am
Hi Nickolas,
Thanks for the great feedback and suggestion for BuyMeACoffee profile. Here’s the link to my profile 😉
https://www.buymeacoffee.com/saurabhdashora
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!