March 31, 2023
Building a REST API with NestJS and Prisma: Authentication
Welcome to the fifth tutorial in this series about building a REST API with NestJS, Prisma and PostgreSQL! In this tutorial, you will learn how to implement JWT authentication in your NestJS REST API.
Table Of Contents
Introduction
In the previous chapter of this series, you learned how to handle relational data in your NestJS REST API. You created a User
model and added a one-to-many relationship between User
and Article
models. You also implemented the CRUD endpoints for the User
model.
In this chapter, you will learn how to add authentication to your API using a package called Passport:
- First, you will implement JSON Web Token (JWT) based authentication using a library called Passport.
- Next, you will protect the passwords stored in your database by hashing them using the bcrypt library.
In this tutorial, you will use the API built in the last chapter.
Development environment
To follow along with this tutorial, you will be expected to:
- ... have Node.js installed.
- ... have Docker and Docker Compose installed. If you are using Linux, please make sure your Docker version is 20.10.0 or higher. You can check your Docker version by running
docker version
in the terminal. - ... optionally have the Prisma VS Code Extension installed. The Prisma VS Code extension adds some really nice IntelliSense and syntax highlighting for Prisma.
- ... optionally have access to a Unix shell (like the terminal/shell in Linux and macOS) to run the commands provided in this series.
If you don't have a Unix shell (for example, you are on a Windows machine), you can still follow along, but the shell commands may need to be modified for your machine.
Clone the repository
The starting point for this tutorial is the ending of chapter two of this series. It contains a rudimentary REST API built with NestJS.
The starting point for this tutorial is available in the end-validation
branch of the GitHub repository. To get started, clone the repository and checkout the end-validation
branch:
Now, perform the following actions to get started:
- Navigate to the cloned directory:
- Install dependencies:
- Start the PostgreSQL database with Docker:
- Apply database migrations:
- Start the project:
Note: Step 4 will also generate Prisma Client and seed the database.
Now, you should be able to access the API documentation at http://localhost:3000/api/
.
Project structure and files
The repository you cloned should have the following structure:
Note: You might notice that this folder comes with a
test
directory as well. Testing won't be covered in this tutorial. However, if you want to learn about how best practices for testing your applications with Prisma, be sure to check out this tutorial series: The Ultimate Guide to Testing with Prisma
The notable files and directories in this repository are:
- The
src
directory contains the source code for the application. There are three modules:- The
app
module is situated in the root of thesrc
directory and is the entry point of the application. It is responsible for starting the web server. - The
prisma
module contains Prisma Client, your interface to the database. - The
articles
module defines the endpoints for the/articles
route and accompanying business logic. - The
users
module defines the endpoints for the/users
route and accompanying business logic.
- The
- The
prisma
folder has the following:- The
schema.prisma
file defines the database schema. - The
migrations
directory contains the database migration history. - The
seed.ts
file contains a script to seed your development database with dummy data.
- The
- The
docker-compose.yml
file defines the Docker image for your PostgreSQL database. - The
.env
file contains the database connection string for your PostgreSQL database.
Note: For more information about these components, go through chapter one of this tutorial series.
Implement authentication in your REST API
In this section, you will implement the bulk of the authentication logic for your REST API. By the end of this section, the following endpoints will be auth protected 🔒:
GET /users
GET /users/:id
PATCH /users/:id
DELETE /users/:id
There are two main types of authentication used on the web: session-based authentication and token-based authentication. In this tutorial, you will implement token-based authentication using JSON Web Tokens (JWT).
Note: This short video explains the basics of both kinds of authentication.
To get started, create a new auth
module in your application. Run the following command to generate a new module:
You will be given a few CLI prompts. Answer the questions accordingly:
What name would you like to use for this resource (plural, e.g., "users")?
authWhat transport layer do you use?
REST APIWould you like to generate CRUD entry points?
No
You should now find a new auth
module in the src/auth
directory.
Install and configure passport
passport
is a popular authentication library for Node.js applications. It is highly configurable and supports a wide range of authentication strategies. It is meant to be used with the Express web framework, which NestJS is built on. NestJS has a first-party integration with passport
called @nestjs/passport
that makes it easy to use in your NestJS application.
Get started by installing the following packages:
Now that you have installed the required packages, you can configure passport
in your application. Open the src/auth.module.ts
file and add the following code:
The @nestjs/passport
module provides a PassportModule
that you can import into your application. The PassportModule
is a wrapper around the passport
library that provides NestJS specific utilities. You can read more about the PassportModule
in the official documentation.
You also configured a JwtModule
that you will use to generate and verify JWTs. The JwtModule
is a wrapper around the jsonwebtoken
library. The secret
provides a secret key that is used to sign the JWTs. The expiresIn
object defines the expiration time of the JWTs. It is currently set to 5 minutes.
Note: Remember to generate a new token if the previous one has expired.
You can use the jwtSecret
shown in the code snippet or generate your own using OpenSSL.
Note: In a real application, you should never store the secret directly in your codebase. NestJS provides the
@nestjs/config
package for loading secrets from environment variables. You can read more about it in the official documentation.
Implement a POST /auth/login
endpoint
The POST /login
endpoint will be used to authenticate users. It will accept a username and password and return a JWT if the credentials are valid. First you create a LoginDto
class that will define the shape of the request body.
Create a new file called login.dto.ts
inside the src/auth/dto
directory:
Now define the LoginDto
class with a email
and password
field:
You will also need to define a new AuthEntity
that will describe the shape of the JWT payload. Create a new file called auth.entity.ts
inside the src/auth/entity
directory:
Now define the AuthEntity
in this file:
The AuthEntity
just has a single string field called accessToken
, which will contain the JWT.
Now create a new login
method inside AuthService
:
The login
method first fetches a user with the given email. If no user is found, it throws a NotFoundException
. If a user is found, it checks if the password is correct. If the password is incorrect, it throws a UnauthorizedException
. If the password is correct, it generates a JWT containing the user's ID and returns it.
Now create the POST /auth/login
method inside AuthController
:
Now you should have a new POST /auth/login
endpoint in your API.
Go to the http://localhost:3000/api
page and try the POST /auth/login
endpoint. Provide the credentials of a user that you created in your seed script
You can use the following request body:
After executing the request you should get a JWT in the response.
In the next section, you will use this token to authenticate users.
Implement JWT authentication strategy
In Passport, a strategy is responsible for authenticating requests, which it accomplishes by implementing an authentication mechanism. In this section, you will implement a JWT authentication strategy that will be used to authenticate users.
You will not be using the passport
package directly, but rather interact with the wrapper package @nestjs/passport
, which will call the passport
package under the hood. To configure a strategy with @nestjs/passport
, you need to create a class that extends the PassportStrategy
class. You will need to do two main things in this class:
- You will pass JWT strategy specific options and configuration to the
super()
method in the constructor. - A
validate()
callback method that will interact with your database to fetch a user based on the JWT payload. If a user is found, thevalidate()
method is expected to return the user object.
First create a new file called jwt.strategy.ts
inside the src/auth/strategy
directory:
Now implement the JwtStrategy
class:
You have created a JwtStrategy
class that extends the PassportStrategy
class. The PassportStrategy
class takes two arguments: a strategy implementation and the name of the strategy. Here you are using a predefined strategy from the passport-jwt
library.
You are passing some options to the super()
method in the constructor. The jwtFromRequest
option expects a method that can be used to extract the JWT from the request. In this case, you will use the standard approach of supplying a bearer token in the Authorization header of our API requests. The secretOrKey
option tells the strategy what secret to use to verify the JWT. There are many more options, which you can read about in the passport-jwt
repository..
For the passport-jwt
, Passport first verifies the JWT's signature and decodes the JSON. The decoded JSON is then passed to the validate()
method. Based on the way JWT signing works, you're guaranteed receiving a valid token that was previously signed and issued by your app. The validate()
method is expected to return a user object. If the user is not found, the validate()
method throws an error.
Note: Passport can be quite confusing. It's helpful to think of Passport as a mini framework in itself that abstracts the authentication process into a few steps that can be customized with strategies and configuration options. I reccomend reading the NestJS Passport recipe to learn more about how to use Passport with NestJS.
Add the new JwtStrategy
as a provider in the AuthModule
:
Now the JwtStrategy
can be used by other modules. You have also added the UsersModule
in the imports
, because the UsersService
is being used in the JwtStrategy
class.
To make UsersService
accessible in the JwtStrategy
class, you also need to add it in the exports
of the UsersModule
:
Implement JWT auth guard
Guards are a NestJS construct that determines whether a request should be allowed to proceed or not. In this section, you will implement a custom JwtAuthGuard
that will be used to protect routes that require authentication.
Create a new file called jwt-auth.guard.ts
inside the src/auth
directory:
Now implement the JwtAuthGuard
class:
The AuthGuard
class expects the name of a strategy. In this case, you are using the JwtStrategy
that you implemented in the previous section, which is named jwt
.
You can now use this guard as a decorator to protect your endpoints. Add the JwtAuthGuard
to routes in the UsersController
:
If you try to query any of these endpoints without authentication it will no longer work.
Integrate authentication in Swagger
Currently there's no indication on Swagger that these endpoints are auth protected. You can add a @ApiBearerAuth()
decorator to the controller to indicate that authentication is required:
Now, auth protected endpoints should have a lock icon in Swagger 🔓
It's currently not possible to "authenticate" yourself directly in Swagger so you can test these endpoints. To do this, you can add the .addBearerAuth()
method call to the SwaggerModule
setup in main.ts
:
You can now add a token by clicking on the Authorize button in Swagger. Swagger will add the token to your requests so you can query the protected endpoints.
Note: You can generate a token by sending a
POST
request to/auth/login
endpoint with a validpassword
.
Try it out yourself.
Hashing passwords
Currently, the User.password
field is stored in plain text. This is a security risk because if the database is compromised, so are all the passwords. To fix this, you can hash the passwords before storing them in the database.
You can use the bcrypt
cryptography library to hash passwords. Install it with npm
:
First, you will update the create
and update
methods in the UsersService
to hash the password before storing it in the database:
The bcrypt.hash
function accepts two arguments: the input string to the hash function and the number of rounds of hashing (also known as cost factor). Increasing the rounds of hashing increases the time it takes to calculate the hash. There is a trade off here between security and performance. With more rounds of hashing, it takes more time to calculate the hash, which helps prevent brute force attacks. However, more rounds of hashing also mean more time to calculate the hash when a user logs in. This stack overflow answer has a good discussion on this topic.
bcrypt
also automatically uses another technique called salting to make it harder to brute force the hash. Salting is a technique where a random string is added to the input string before hashing. This way, attackers cannot use a table of precomputed hashes to crack the password, as each password has a different salt value.
You also need to update your database seed script to hash the passwords before inserting them into the database:
Run the seed script with npx prisma db seed
and you should see that the passwords stored in the database are now hashed.
The value of the password
field will be different for you since a different salt value is used each time. The important thing is that the value is now a hashed string.
Now, if you try to use the login
with the correct password, you will face a HTTP 401
error. This is because the login
method tries to compare the plaintext password from the user request with the hashed password in the database. Update the login
method to use hashed passwords:
You can now login with the correct password and get a JWT in the response.
Summary and final remarks
In this chapter, you learned how to implement JWT authentication in your NestJS REST API. You also learned about salting passwords and integrating authentication with Swagger.
You can find the finished code for this tutorial in the end-authentication
branch of the GitHub repository. Please feel free to raise an issue in the repository or submit a PR if you notice a problem. You can also reach out to me directly on Twitter.
Don’t miss the next post!
Sign up for the Prisma Newsletter