Building two-factor authentication with NestJS and Postgres
Tags: postgres, nestjs, softwaredevelopment, technology
Published: 2022-01-28 22:01:41

## Introduction

Cybercrime and hostile operations against public and private entities have become more prevalent in recent years. This rise in risk explains why many software companies are adding an extra layer of security to their customers' accounts.

2FA is an extra layer of security that confirms that the person seeking to get into an online account is who they say they are. A user's username and password must be entered first. They will then be asked to provide additional details before being granted access. This approach will protect a compromised account from fraudulent activities. Even if a hacker discovers the user's password, they will not be able to login into the account because they lack the second-factor authentication ([2FA](https://authy.com/what-is-2fa/)) code.

This tutorial will teach you how to implement 2FA authentication in a [NestJS](https://nestjs.com/) application. Grab the [code from Github](https://github.com/Claradev32/Nest2faAuthentication) at any time. Let’s get started!

## Prerequisites

This tutorial is a hands-on demonstration. To follow along, ensure you have the following installed:

- [Nodejs](https://nodejs.org/en/) - Nodejs is the runtime environment for our application.
- [Postgres database](https://www.postgresql.org/download/) - We’ll save the user’s records in a Postgres database.
- [Arctype](https://arctype.com/) - We’ll use a Postgres GUI to help with user authentication.

## Create a Nest application

Let’s start by creating a NestJS application for our project. Before we do that, we’ll install the Nest CLI with the command below:

    npm i -g @nestjs/cli

Then, create a Nest application with the command below.

    nest new authentication

Wait for some time for the installation to complete before proceeding to the next step.

## Installing dependencies

Now, let’s install the dependencies for this project. We’ll start with the dev dependencies using the command below.

```
npm i -D @types/bcrypt @types/nodemailer
```

Then we’ll add our other dependencies.

```
npm i bcrypt  @nestjs/jwt @nestjs-modules/mailer nodemailer hbs @nestjs/typeorm typeorm pg
```

This will take a little bit of time to install, so wait for it to finish. When it’s done, it’s time to set up a database for our application.

## Setup Postgres database

At this point, we have installed all the dependencies we need for this project. Now let’s go ahead and set up our Postgres database. We’ll use the TypeORM Postgres Object Relational Mapper to connect our application to the Postgres database. Run the commands below to set up a Postgres database.
![](https://arctype.com/blog/content/images/2022/01/image-12.png)

```
sudo su - postgres
psql
create database  authentication
```

![](https://arctype.com/blog/content/images/2022/01/image-13.png)

![](https://arctype.com/blog/content/images/2022/01/image-14.png)
    
```
create user authentication with encrypted password authentication
grant all privileges on database authentication to authentication
```
Next, open the **/src/app.module.ts** file import the **TypeOrmModule,** and connect to the database using the **forRoot** method with the code snippet below.

```
…
import { TypeOrmModule } from '@nestjs/typeorm';
imports: [
   TypeOrmModule.forRoot({
     type: 'postgres',
     host: 'localhost',
     username: 'postgres',
     password: 'authentication',
     database: 'authentication',
     entities: [User],
     synchronize: true,
   }),
TypeOrmModule.forFeature([User]),
],
…
```

In the code snippet above, notice that we passed in the **User** entity, but have yet to create it. Don’t worry - we’ll create this entity in a subsequent section. Also, notice that we used the **forFeature()** method to define which repository is registered in the current scope, which lets **TypeORM** know about the **User** entity.

Now, let’s create the **User** entity to define our models in the database.

## Create the User entity

At this point, our application is connected to the Postgres database. Now we’ll create a **User** entity to represent the user data we’ll store in the database. First, create an **app.entity.ts** file in the **src** folder and add the code snippet below.

```
import { Entity, Column, PrimaryGeneratedColumn, PrimaryColumn, CreateDateColumn } from 'typeorm';

@Entity()
export class User {
   @PrimaryGeneratedColumn("uuid")
   id: number;

   @Column()
   fullname: string;

   @Column({unique:true})
   email: string;

   @Column()
   password: string

   @Column({ select: false, nullable: true })
   authConfirmToken: String

   @Column({ default: false, nullable: true })
   isVerified: Boolean;

   @CreateDateColumn()
   createdAt: Date;   
}
```

In the above code snippet, we created an entity by defining a User class. We did this by defining the properties of the User entity using the **Column**, **PrimaryGeneratedColumn**, and **DateCreatedColumn** decorators. The **PrimaryGeneratedColumn** decorator will generate random ids for the users using [**UUID**](https://www.npmjs.com/package/uuid) module. We added the **unique** property to our email **Column** to ensure no user registered with the same email twice. Lastly, the **DateCreatedColumn** decorator will add a date by default when a record is created.

Next, open the app.module.ts file and import the User entity. This import resolves the error showing on the app.module.ts file.

```
import { User } from './app.entity'
```

Our User entity is set. Now, let's create the controllers to handle the user's requests.

## Create the app service

At this point, our User entity is set. Now let’s set up our app service by setting our route handler functions. Open the app.service.ts file and the required modules.

```
import { Injectable, HttpException, HttpStatus } from "@nestjs/common";
import { User } from "./app.entity"
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
import { MailerService } from '@nestjs-modules/mailer';

…
```

In the above code snippet, we import the several vital elements:

- The **@Injectable** decorator, which makes our **appService** class available to managed by the Nest IoC container
- **HttpException,** which lets us create custom errors 
- **HttpStatus,** which sends custom status codes
- The **User** entity (described above)
- And **InjectRepository,** which injects our User entity to the **appService** class. 

Also, we import **bcrypt**, **JwtService**, and **MailerService**, which we’ll configure in our app module later in this section.

In addition, we create a global **code** variable to store the random verification code that will be sent to the users after registration. We generate a random code and assign it to the code variable, and then create the **sendConfirmationEmail** and **sendConfirmedEmail** methods to send confirmation and verification emails to registered users.

```
@Injectable()
export class AppService {

 private code;

 constructor(@InjectRepository(User) private userRepository: Repository<User>, private mailerService: MailerService) {
   this.code = Math.floor(10000 + Math.random() * 90000);
 }

 async sendConfirmedEmail(user: User) {
   const { email, fullname } = user
   await this.mailerService.sendMail({
     to: email,
     subject: 'Welcome to Nice App! Email Confirmed',
     template: 'confirmed',
     context: {
       fullname,
       email
     },
   });
 }

 async sendConfirmationEmail(user: any) {
   const { email, fullname } = await user
   await this.mailerService.sendMail({
     to: email,
     subject: 'Welcome to Nice App! Confirm Email',
     template: 'confirm',
     context: {
       fullname,
       code: this.code
     },
   });
 }
…
```
Next, we create the **signup** method to handle the user’s registration. We do this with the code snippet below.

```
async signup(user: User): Promise<any> {
try{
   const salt = await bcrypt.genSalt();
   const hash = await bcrypt.hash(user.password, salt);
   const reqBody = {
     fullname: user.fullname,
     email: user.email,
     password: hash,
     authConfirmToken: this.code,
   }
   const newUser = this.userRepository.insert(reqBody);
   await this.sendConfirmationEmail(reqBody);
   return true
}catch(e){
    return new HttpException(e, HttpStatus.INTERNAL_SERVER_ERROR);
}
 }
…
```

Our **signup** method is an asynchronous function that returns `true` when an account is created. We generate a salt value using the bcrypt **genSalt ()** method and hash the user's password using the **hash** method. Then we store the hashed version of the user's password and create a new object using the **userRepository** insert method. Next, we call the **signin** method, which is an asynchronous function that returns the JWT token or an HTTP exception via the code snippet below:

```
async signin(user: User, jwt: JwtService): Promise<any> {
try{
   const foundUser = await this.userRepository.findOne({ email: user.email });
   if (foundUser) {
     if (foundUser.isVerified) {
       if (bcrypt.compare(user.password, foundUser.password)) {
         const payload = { email: user.email };
         return {
           token: jwt.sign(payload),
         };
       }
     } else {
       return new HttpException('Please verify your account', HttpStatus.UNAUTHORIZED)
     }
     return new HttpException('Incorrect username or password', HttpStatus.UNAUTHORIZED)
   }
   return new HttpException('Incorrect username or password', HttpStatus.UNAUTHORIZED)
}catch(e){
return new HttpException(e, HttpStatus.INTERNAL_SERVER_ERROR);
}
 }
…
```

Our **signin** method uses the user’s email address to check if their record exists in our database. If the user is found, we use the bcrypt `compare` method to check if the user's password matches the hashed password stored in the database. Then generate and send a JWT token to the user. If no record matches the query, we’ll return a corresponding error message.

Next, we’ll create a **verify** method, which is an asynchronous function that returns true or an error when a user is verified.

```
async verifyAccount(code: String): Promise<any> {
try{
   const user = await this.userRepository.findOne({
     authConfirmToken: code
   });
   if (!user) {
     return new HttpException('Verification code has expired or not found', HttpStatus.UNAUTHORIZED)
   }
   await this.userRepository.update({ authConfirmToken: user.authConfirmToken }, { isVerified: true, authConfirmToken: undefined })
   await this.sendConfirmedEmail(user)
   return true
}catch(e){
   return new HttpException(e, HttpStatus.INTERNAL_SERVER_ERROR)
}
}
```

In our verify method, we query the database for a user with the code in the request body. If no user matches the search, we return an HTTP exception. Otherwise, we update the user's **isVerified** property to `true` and reset the **authConfirmToken** to undefined to make it empty.

Let's open the **app.module.ts** file and configure the **JwtService** and **MailerService.** First, import the **JwtModule**, **MailerModule**, **ConfigModule**, and **HandlebarsAdapter**, which we’ll use to configure our email templates. The **ConfigModule** will enable us to load our environment variables like the JWT secret that will be created later in this section.

```
import { ConfigModule} from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { MailerModule } from '@nestjs-modules/mailer';
import { join } from 'path';
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
…
```

Create a **.env** file in the project root directory to store your JWT secret. You can generate one using the built-in crypto module.

```
require('crypto').randomBytes(64).toString('hex')
```

And store the generated secret in the **.env** file you created.

```
JWT_SECRET = [your secret goes here]
```

## Setup the mailer module

Now append the code snippets below in the app module imports array to load the environment variables. We’ll also configure the **JwtModule**, **MailerModule**, and **HandlebarsAdapter.**

```
imports: [
   …

   ConfigModule.forRoot(),
   JwtModule.register({
     secret: process.env.JWT_SECRET,
     signOptions: { expiresIn: '60s' },
   }),
   MailerModule.forRoot({
       transport: {
       service: "gmail",
       secure: false,
       auth: {
         user: 'your email address',
         pass: 'your email password',
       },
     },
     defaults: {
       from: '"No Reply" <youremail@gmail.com>',
     },
     template: {
       dir: join(__dirname, "../views/email-templates"),
       adapter: new HandlebarsAdapter(), 
       options: {
         strict: true,
       },
     },
   }),
…
```

## Create the app controllers

At this point, our app service is set. Now let’s set up our app controllers to handle incoming requests. Open the **app.controller.ts** file and import the required modules.

```
import { Controller, Get, Post, Render, Res, Body, HttpStatus, Req, Param } from '@nestjs/common';
import { AppService } from './app.service';
import { User } from './app.entity';
import { JwtService } from '@nestjs/jwt
```
Then we’ll use the **@Controller** method to define our app controllers. First we’ll create an **AppController** class with a constructor method. We create two private parameters for our **appService** class and the **JwtService**.

Then we create our **Root** and **VerifyEmail** routes, which will listen to a **Get** request using the **@Get** decorator, and render the index and the verify templates, which will be set up later in this section using the **@Render** decorator.

```
@Get()
 @Render('index')
 root() { }

 @Get('/verify')
 @Render('verify')
 VerifyEmail() { }
```  

Next, we create the **Signup** route which will listen to Post requests coming to **/signup** endpoint. The **Signup** controller gets the input from the user's form and matches it with the user entity we created. Then it awaits the result of the **appService** signup method, which takes the user object as a parameter.

```
@Post('/signup')
 async Signup(@Body() user: User) {
   return  await this.appService.signup(user);
 }
```

Next, we create the **Signin** route which will listen to Post requests coming to **/signin** endpoint . The **Signin** controller gets the input from the user's form and matches it with the user entity we created. Then await the result of the **appService** signin method, which also takes the user object form object as a parameter.

```
 @Post('/signin')
 async Signup(@Body() user: User) {
  return await this.appService.signin(user);
 }
…
```

Then we create a **Verify** route and await the result from the **appService** **verifyAccount** method, which takes the user confirmation code as a parameter.

``` @Post('/verify')
 async Verify(@Body() body) {
   return await this.appService.verifyAccount(body.code)
 }
…
```

Lastly, open the main.ts file, delete the boilerplate code and add the following code snippets below to set up our [template engine](https://www.educative.io/edpresso/what-are-template-engines) and static files director to enable [server-side rendering](https://www.educative.io/edpresso/what-is-server-side-rendering) in our application.

```
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
import { AppModule } from './app.module';

async function bootstrap() {
 const app = await NestFactory.create<NestExpressApplication>(
   AppModule,
 );

 app.useStaticAssets(join(__dirname, '..', 'public'));
 app.setBaseViewsDir(join(__dirname, '..', 'views'));
 app.setViewEngine('hbs');

 await app.listen(3001);
}
bootstrap()
```  

## Create the email templates

With our view engine and static files configured, let’s go create our templates. First, create a **views** folder in the project root directory, and in the **views** folder create an **email-templates** folder. Create an **index.hbs** and a **verify.hbs** files in the views folder. Then create a **confirm.hbs** and **confirmed.hbs** files in the email-templates folder. Open the **view/index.hbs** file and add the code snippet below.

```
<!DOCTYPE html>
<html lang="en">
<head>
   <!-- Required meta tags-->
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
   <meta name="description" content="Colorlib Templates">
   <meta name="author" content="Colorlib">
   <meta name="keywords" content="Colorlib Templates">
   <!-- Title Page-->
   <title>Signup to meet new people</title>
   <!-- Font special for pages-->
   <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i,800,800i" rel="stylesheet">
   <!-- Main CSS-->
   <link href="/css/main.css" rel="stylesheet" media="all">
</head>
<body>
   <div class="page-wrapper bg-dark p-t-100 p-b-50">
       <div class="wrapper wrapper--w900">
           <div class="card card-6">
               <div class="card-heading">
                   <h2 class="title">Signup</h2>
               </div>
               <div class="card-body">
                   <form method="POST" id="form">
                       <div class="form-row">
                           <div class="name">Full name</div>
                           <div class="value">
                               <input class="input--style-6" type="text" name="full_name" id="fullname">
                           </div>
                       </div>
                       <div class="form-row">
                           <div class="name">Email address</div>
                           <div class="value">
                               <div class="input-group">
                                   <input class="input--style-6" type="email" id="email" placeholder="">
                               </div>
                           </div>
                       </div>
                       <div class="form-row">
                           <div class="name">Password</div>
                           <div class="value">
                               <div class="input-group">
                                   <input class="input--style-6" type="password" id="password" placeholder="">
                               </div>
                           </div>
                       </div>
                   </form>
               </div>
               <div class="card-footer">
                   <button class="btn btn--radius-2 btn--blue-2" type="submit" onclick="signForm()">Signup</button>
               </div>
           </div>
       </div>
   </div>
   <!-- Jquery JS-->
   <script src="/vendor/jquery/jquery.min.js"></script>
   <script src="/js/global.js"></script>
   <script src="/js/index.js" async></script>
</body>
</html>
```

Open the **verify.hbs** file and add the code snippet below:

```
<!DOCTYPE html>
<html lang="en">

<head>
   <!-- Required meta tags-->
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
   <meta name="description" content="Colorlib Templates">
   <meta name="author" content="Colorlib">
   <meta name="keywords" content="Colorlib Templates">

   <!-- Title Page-->
   <title>Apply for job by Colorlib</title>

   <!-- Font special for pages-->
   <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i,800,800i" rel="stylesheet">
   <!-- Main CSS-->
   <link href="/css/main.css" rel="stylesheet" media="all">
</head>
<body>
   <div class="page-wrapper bg-dark p-t-100 p-b-50">
       <div class="wrapper wrapper--w900">
           <div class="card card-6">
               <div class="card-heading">
                   <h2 class="title">Confirm Email</h2>
               </div>
               <div class="card-body">
                   <form method="POST" id="form">
                       <div class="form-row">
                           <div class="name">Confirmation Code</div>
                           <div class="value">
                               <input class="input--style-6" type="text" id="code">
                           </div>
                       </div>
                   </form>
               </div>
               <div class="card-footer">
                   <button class="btn btn--radius-2 btn--blue-2" type="submit" onclick="codeForm()">Confirm</button>
               </div>
           </div>
       </div>
   </div>

   <!-- Jquery JS-->
   <script src="/vendor/jquery/jquery.min.js"></script>
   <!-- Main JS-->
   <script src="/js/global.js"></script>
    <script src="/js/index.js"></script>
</body>
</html>
<!-- end document-->
```

Add the code snippet code below to the **email-templates/confirm.hbs** file.

```
<p>Hey {{ fullname }},</p>
<p>Verify your email with code below</p>
<p>Your verification code is: {{code}}</p>

<p>If you did not request this email you can safely ignore it.</p>
```

And the code snippet below to the **email-templates/confirmed.hbs** file.

```
<p>Hey {{ fullname }},</p>
<p>Your account for {{email}} has been confirmed!</p>

<p>If you did not request this email you can safely ignore it.</p>
```

Next, create a **public** folder in the project root directory for our static files, then create a **js** folder inside it. Inside that, create an `index.js` file with code snippet below:

```
function signForm() {
   const form = document.getElementById('form');
   const email = document.getElementById('email').value;
   const fullname = document.getElementById('fullname').value;
   const password = document.getElementById('password').value;

   fetch('http://localhost:3001/signup', {
       method: "POST",
       body: JSON.stringify({
           fullname,
           email,
           password
       }),
       headers: {
           "Content-type": "application/json"
       }
   }).then(data => data.json())
       .then(res => {
           if (res.status === 500) {
                alert("An error occurred, try again")
           } else {
               alert("Your account has been created, We sent a verification code to Email");
               form.reset();
               window.location.href = "/verify"
             }
       })

}

function codeForm() {
   const form = document.getElementById('form');
   const code = document.getElementById('code').value;

   fetch('http://localhost:3001/verify', {
       method: "POST",
       body: JSON.stringify({
           code
       }),
       headers: {
           "Content-type": "application/json"
       }
   }).then(data => data.json())
       .then(res => {
           if (res.status === 400) {
                 alert(res.response)
            } else {
                alert("Your account has been verified, proceed to the signin page");
                form.reset();
           }
       })
}
```

The above code snippets make a post request to our **/signup** and **/verify** endpoint to register and to confirm a user's email.

Lastly, get the other static files from the [Github](https://github.com/Claradev32/Nest2faAuthentication/tree/master/public) repository for this project, and add them also to the public folder.

## Enable Google LSAA

With our email templates setup, we should be able to send emails to our users. We're going to be using Gmail to send the emails in this tutorial. So, we need to configure our Gmail account to allow email from Less secure app access. Follow the steps below to enable LSAA on your Gmail account.

1. Open Chrome. Click on the profile icon on the top right-hand side of your browser.

![Screenshot of Chrome.](https://lh3.googleusercontent.com/VgFeTSsjUBUvZkUa9ivfjC8f-XVjr9vUzhDWtJaJQFH_jTSsA9Q9o-ErhQX1JeMs199dBL6fWEHNCBFng5wadt6bas7zTwha_WoseB99jrrP-UG4mlG8lfYR6dLcyuqlMzH3gbGZ)

1. Click on **Manage your Google Account.**
2. Type **less** on the search box, and click on **less secure app access.**

![Screenshot of Google account window.](https://lh3.googleusercontent.com/NYsldWz-Kmz0KotXvqfmzwjHa97trdLrRTR3AGHduyuz-BL3-7ErHOSAlAp6YNu-o7x_8oBIl0JSfHblBTFDCWit19H5wWvTK66GR_J7JNtDnuV0pxnb6_PnUlRAe9YYAptrfFEv)

1. Toggle the **Allow less secure apps**: ON input box to enable it.

![Screenshot of Google accounts setting.](https://lh4.googleusercontent.com/FzNS0gFEmaXMoNZHWRBPMvP0CrGLINXcgn7GugBH0ARj9I_Y7P-eJXf8tX5f4UiWQCA-KqDPPPKKCHyi7IoSotI3DumXE9BsLkGFsDjHxiAjLSgyw8QmE26y1m-RRk5WDE0U3SLM)

Now let’s run the application and get it tested.

## Test the Application

At this point, our application is ready. Let’s test it out. In your terminal, change the directory so that you're in the authentication folder and run the server with the command below.

```
#Change directory
cd authentication 

#Start the server
npm run start:dev
```

Then go to [http://localhost:3001/](http://localhost:3001/) to view the index page. You should see the result shown below.

![The signup form (screenshot)](https://lh6.googleusercontent.com/uV23He4wfTcM4LxpozAaG1J5MrDC3I_rWL_QgZTzcgCXsabUoTavkpCyswLvmYc5h05wSszbaHUTKpnq6oN9fig1f88DlUOLWyw7W2QL5TvNnobbJLR7UvCvS49cgwdXoK20Qnw2)

Fill in the fields and sign up.  You’ll be asked to verify your account. Check your email for the confirmation code. Verify the code on the verify page.

![Screenshot of confirmation code.](https://lh4.googleusercontent.com/CRggT9t8aoGlZ4ATBlCO3tqjZneMOOdHvvbRb0mBzhAmcFYtamfGpOpSxxTAbiZanQV5yi7iKxy_dyxxIvikPJ3N8B_7XaNNl8iFkdU9KTxvqyrvKaBE_6bu3slkmjk7mt1dZ0of)

Success! We have a working 2FA application, as desired.

Got stuck? Have any issues? The code for this tutorial is fully available on [Github](https://github.com/Claradev32/Nest2faAuthentication) if needed.

## Conclusion

By building a demo project, we’ve learned how to implement 2FA authentication in a NestJS application. We started with the introduction of 2FA authentication concepts and learned how to create a NestJS application that puts them into practice. Now that you’ve gotten the knowledge you seek, how would you increase the security of your next NestJS project? Perhaps, you can learn more about Nest from the [official website](https://docs.nestjs.com/) and take things even further.