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.
- 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
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
Setting up NestJs
Navigate to a preferred project location, open your terminal, and scaffold a new project using Nest CLI.
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).
Open your terminal and run the project using
npm run start:dev
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.
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.
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
Your folder structure should look like this
typeorm.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.
Entities represent tables on the database.
Let's now create a user entity file
user.entity.ts in our auth directory. Updated folder structure
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
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
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.
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 our
user.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
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.
auth-signup.dto.ts as follows
We can now use this DTO in
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.
Now we can use our user repository to create a signUp method:
We will use the updated service to create methods in our
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.tsthat 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.
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.tsto 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.
Now we can use signIn method created earlier in the
auth.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
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
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.
We can now use the updated service to create signIn method in our
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:
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
If you liked this post, Feedback, claps, and comments are highly appreciated.