How to create an Authentication Module with JWT in NestJs
Preliminary
As a typical web developer, there are times when we need some form of protection against unauthorized access to information on our application. Companies like Okta, AuthO e.t.c, offer authentication-as-a-service with tons of documentation on how it can be integrated into applications (frontend or backend).
I recently kicked off my journey into backend development and I figured at some point, I might be required to build or maintain a custom authentication codebase.
In this article, I will show you how to create a custom authentication module with JWT in NestJs.
Why NestJs?
There are lots of libraries and frameworks built on javascript that enables us to create server-side applications. However, I have found NestJs to be a better option for the following reason:
- It is a progressive Node.js framework for building a scalable application
- Good modular architecture for the project
- Allows integration of third-party libraries
- Uses ExpressJs under the wood
Prerequisites
We will be needing the following:
- Nodejs installed on your PC (Follow this link for installation steps)
- NestJs CLI setup on your PC (You can use this link for installation steps)
- PostgreSQL and PgAdmin installed (You can try out other databases of your choice)
- Postman installed (For testing endpoints)
- Basic knowledge of Typescript and PostgreSQL (or any other database)
You can check my repository for this project
https://github.com/Mr-Malomz/auth-nestjs
Setting up NestJs
Step 1.
Navigate to a preferred project location, open your terminal, and scaffold a new project using Nest CLI.
Step 2.
Select the preferred package manager for the installation(npm or yarn), and change the directory to the project folder using the command below
Nest CLI creates a project
directory, node modules, and a few other boilerplate files, and a src/
directory will be created and populated with several core files.
Let’s go over some of the directories and files from above:
node_modules:
collection of libraries downloaded from npm.src/app.controller.ts:
a basic controller with a single route. Controllers generally handle incoming and outgoing requests.src/app.controller.spec.ts:
for writing unit-test (beyond the scope of this tutorial).src/app.module.ts:
the root module of the application. Modules in programming are used to divide your programs into smaller units.src/app.service.ts:
a class used to abstract all business logic away from the controller. Service helps us keep our controller clean(processing only request).test/app.e2e-spec.ts:
for writing end-to-end tests (beyond the scope of this tutorial).
Step 3.
Open your terminal and run the project usingnpm run start:dev
Open http://localhost:3000
on your preferred browser
You should see “Hello World” on your browser if everything goes well
Scaffold Auth Module
We need to scaffold our auth module using Nest CLI by running the command below.
The module will serve as an entry point for configuring properties related to our authentication.
On successful running of the command, an auth folder (stores all files related to auth) will be created.auth.module.ts
contains an Authmodule class for registering services, controller, imports e.t.c.
Nest CLI automatically updates app.module.ts
with AuthModule Class
Next, we need to create a controller and service to handle requests and abstract the controller’s logic respectively. Good news! Nest CLI can scaffold these files for us.
Database Setup
We need a database to save authentication information.
To set up our application database, PostgreSQL and PgAdmin have to be installed.
To begin, we open our PgAdmin. It should look like this.
PS: PostgreSQL default password is postgres if it has not been changed.
Next, right-click on the database and create a new database “authuser”, then save.
Object Relational Mapping and TypeORM
Object Relational Mapping (ORM) is a programming technique used for querying and manipulating data from a database. ORM lets you use a programming language to interact with a database by writing little or no SQL.
TypeORM is an ORM library that runs in Node.js and can be used with TypeScript or JavaScript.
Now that we have covered the definition of ORM and TypeORM, we will now proceed to connect our nest.js application to our database.
First, we need to install the required dependencies using your terminal
@nestjs/typeorm:
It is a Nestjs wrapper that helps integrate TypeORM.
typeorm:
This installs TypeORM.
pg:
This installs PostgreSQL driver used by TypeORM to connect to the database.
PS: TypeORM also supports other database integration.
Now that our dependencies have been successfully installed, we need to initialize the database connection in our root module (app.module.ts
). Before doing this, let us create a config folder and a TypeScript file to save our configuration.
Create a folder inside src folder called config and then create a file typeorm.config.ts
.
Your folder structure should look like this
Open thistypeorm.config.ts
file, then copy and paste the configuration code below
Let’s explain this code a bit
TypeORMModuleOptions:
an interface that shows us all options related to configuring our database.
type:
Database type. “postgres” since we are using PostgreSQL. This will inform TypeORM to use the pg driver we installed earlier.
host:
“localhost” since we shall be testing locally.
port:
5432 is PostgreSQL database port.
username:
“postgres” by default.
password:
“postgres” by default. If changes have been made to postgreSQL database password, then the password should be used instead of the default password.
database:
“authuser”. The name of the database we created in PgAdmin earlier.
entities:
Entities represent tables in our database. So we provided a regular expression for typeORM to match any folder and files that end with entity.ts in our project directory.
synchronize:
“true” means it should sync up with the schema in the postgreSQL database.
Next, we need to import typeORMConfig created earlier and use it in our app.module.ts
to complete our configuration.
Run the application using npm run star:dev
.
If you notice any error on the terminal, please check the article and make corrections accordingly.
Entity
Entities represent tables on the database.
Let's now create a user entity file user.entity.ts
in our auth directory. Updated folder structure
Now open user.entity.ts
and input the code below
Let’s explain this code a bit:
We started by creating a User class that extends BaseEntity (a class that lets us inherit methods and properties needed for creating an entity provided by TypeORM).
Next, we decorated the class with @Entity() and @Unique([‘email’]). This decorator is used to mark classes that represent a table and also mark columns with unique values (Email in our case).
Next, we defined the columns and properties of the table. Columns can be PrimaryGeneratedColumn, Column e.t.c. They also allow properties like length, regular expression check, nullability, and so on.
Repository Design Pattern
Repository Pattern is a design pattern that hides the details of how data are stored and retrieved from the database. It abstracts the details of data access logic from business logic.
Now let's create a repository file user.repository.ts
in our auth directory. Updated folder structure
Open user.repository.ts
and input the code below
Let’s explain this code a bit:
First, we created a class UserRepository to handle data logic and extend it with TypeORM built-in class Repository that takes in an entity(User in our case).
The decorator @EntityRepository() informs TypeORM that we are creating a custom repository.
To make this repository available in our auth module, we need to inform Nest.js of the repository we intend using in our module by updating the import in auth.module.ts
. This makes our repository available for use via injection throughout our module.
Now we can navigate to auth.service.ts
file and inject UserRepository using dependency injection method. This is a technique that allows an object(auth service) receives information from another object(UserRepository) it depends on.
Updated code in auth.service.ts
The newly created private property called userRepository gives us access to methods and properties associated with our user repository created earlier. With this, we can now start writing our authentication functionality.
Sign Up Process with Authentication
There are steps we need to follow when incorporating authentication in the signup process.
a. Repository
We need to update user.repository.ts
with an async method to handle all data logic needed for sign up. Typically, the signup process involves getting parameters like username, first name, email e.t.c. from users, validating, and storing them on the database. We will make use of email, password, first name, and last name for this tutorial.
We will be needing these parameters in ouruser.repository.ts auth.service.ts
and auth.controller.ts
. We will also create a Data Transfer Object (DTO) to represent them.
Data Transfer Object (DTO) is simply an object that transfers data from one point to another. You can read more here.
Next, we create DTO to handle sign up parameters.
1. Create a DTO folder in the auth folder.
2. Create a auth-signup.dto.ts
file and input the code in the gist auth-signup.dto.ts
We currently do not have any form of validation in our DTO and we need to change that. Nest.js uses Pipes to transform(convert from one data type to another) and validate(evaluate input data for its validity) user’s responses.
Nest.js also supports class-validator library. This powerful library allows us to use decorator-based validation.
We need to install the required dependency before we can start using it.
Now, using a class-validator, we will validate our parameters to ensure that the data sent to the endpoint is in an acceptable format.
Update auth-signup.dto.ts
as follows
We can now use this DTO in user.repository.ts
Let’s update user.repository.ts
with an asynchronous signUp method.
This method takes a parameter of type AuthSignUpDto which we created earlier and returns a promised-based string since our method is asynchronous.
For this, we destructured the authSignUpDto to get the email, password, firstname, and lastname. We also created a new user instance from our User entity class and save destructed parameters accordingly.
Lastly, we wrap the user instance to the database with a try and catch block. We also checked for duplicate email entries using the error code provided by TypeOrm and used an inbuilt exception class provided by Nestjs to handle errors thrown during operation.
With that out of the way, we now have access to the signUp method anywhere we inject (auth.service.ts
in our case) user repository.
b. Service
Now we can use our user repository to create a signUp method:
c. Controller
We will use the updated service to create methods in our auth.controller.ts
file:
We injected our AuthService class edited earlier by creating a private property authService, then we created an asynchronous signUp method that takes in a parameter of type AuthSignUpDto.
This method has two decorators; @Post(‘/signup’) and @Body(). The first decorator informs Nestjs about the HTTP method type of Post and routes to our api endpoint auth/signup
while the second decorator extract parameter from the request object.
d. Postman and PostgreSQL
We will make POST requests to our endpoint using postman and check postgreSQL to see if the data sent is been stored on the database.
e. Hashing Passwords
From the previous section d, you will notice that the password is still stored as plain text. This is not what we want. We want to have an extra security on the saved password and we can achieve this by hashing and using random salt on the passwords.
Hashing involves transforming our password into a different string while Salt is the addition of a random string per user to the existing hashed password.
To hash our password, we need to install the required dependency using the command below:
PS: bcrypt is the main package while @types/bcrypt helps with typescript support.
Next, we will update user.entity.ts
to include an extra column to store random salt per user:
Next, we will create a private method in user.repository.ts
that takes in two parameters (password and salt) and then returns an encrypted password to be stored in the database:
With the private method in place, we can now hash and salt our password before storing it in the database.
First, we generate a random salt using bcrypt inbuilt genSalt() method and then encrypt the password using the newly generated salt.
Finally, we will delete our user table on the database by right-clicking on the user table and selecting Delete/Drop option:
Now let’s test our code by re-starting our application using npm run start:dev
and making a request with Postman.
On refreshing the database, we should see user
table populated with newly created user details:
Sign In Process with Authentication
Now that we have concluded our sign-up process, we will handle the sign-in operation for a registered user.
a. Repository
Create a new DTO to handle processes related to sign-in in our repository, service, and controller:
Sign-In Response Type
During authentication, we might be required to send user’s details along with the token. To do this, we need to create a new dto (SignInResponse) to represent our response type:
Next, create a new method in user.repository.ts
to handle all database activities related to sign-in.
To compare user passwords and limit the rate at which we send requests to the database, let’s create a method in user.entity.ts
to help with validation :
We imported bcrypt into our user entity and created a validatePassword method that receives an incoming password as a parameter, hash the password with the salt in the database, compares it with the saved password, and returns either true or false match.
Next, we update user.repository.ts
file by importing the required DTO’s (AuthSignInDto and SignInResponse) and then create a signIn method that takes in data of type AuthSignInDto and returns data of type SignInResponse.
We will then destructure the DTO to get email and password from the request body.
Next, find the user by email using typeORM in-built method findOne() that takes an object of email.
Then we check if the user exists and the password matches the password in the database using the validatePassword method created earlier. If it does, we populated the response accordingly and if it doesn’t we return null.
b. Service
Now we can use signIn method created earlier in theauth.service.ts
file by creating a method in the service class to handle siginin. Before we create this method, we need to create an interface to represent how our response is returned when users are authenticated:
c. Passport.js and JWT
To complete the sign-in process we need to install some packages to help us with security and token:
Let’s go over the packages above:
@nestjs/jwt:
a wrapper provided by nestjs to ease the use of jwt.@nestjs/passport:
a wrapper provided by nestjs to ease the use of passportjs during authentication.passport:
an authentication middleware.passport-jwt:
a passport strategy for authenticating with jwt.
Now we can start configuring our application to use installed packages.
First, we need to update auth.module.ts
file:
Next, we imported the needed method from @nestjs/passport and @nestjs/jwt. We used PassportModule to register the kind of authentication strategy to use (jwt in our case), then JwtModule was used to register our secret key, and also configure signOption for the token to last for 1 hour (3600 seconds).
PS: It’s not advisable to hard-code secrets in your application. Using an environment variable is advisable for this.
Since we have added jwt module in auth.module.ts
. We can inject jwtService in our auth.service.ts
file:
We created a signIn method that takes in a parameter of type AuthSignInDto and returns a response of type UserJwtResponse. Remember we created both the DTO and the interface earlier.
Then we will create a variable user response referencing the signIn method created in our repository earlier.
We then conditionally check the response of userResponse, if it doesn’t return a user, we throw an UnauthorizedException with invalid credentials.
But if it does, we create a payload variable and pass the response as an object in it.
Next, we created another variable accessToken to generate a token for this user using the injected jwtService.
Finally, we created a variable signInResponse of type UserJwtResponse and pass in our user response and accesstoken respectively. Then we pass it as the return type for our signIn service.
d. Controller
We can now use the updated service to create signIn method in our auth.controller.ts
file:
First, we imported the required DTO and interface.
Then we created a signIn method decorated with Post Http method and “/signin” route.
We also passed in a parameter of type AuthSignInDto and returns data of type UserJwtResponse from our service.
Now, we can test our signIn endpoint using any of our created user in the database:
Conclusion
The authentication module works as expected and can be applied to real projects.
However, this module can be extended further and used alongside other modules that need authentication.
You can check my repository for this project
https://github.com/Mr-Malomz/auth-nestjs
If you liked this post, Feedback, claps, and comments are highly appreciated.
You can connect with me on Twitter and LinkedIn
References
https://docs.nestjs.com/
https://nodejs.org/en/download/
https://www.postgresql.org/download/
https://www.pgadmin.org/download/
https://www.postman.com/downloads/
https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping
https://typeorm.io/#/
https://en.wikipedia.org/wiki/Data_transfer_object
https://github.com/typestack/class-validator
https://docs.nestjs.com/pipes