• Latest
Building Two-Factor Authentication With NestJS and Postgres

Building Two-Factor Authentication With NestJS and Postgres

January 29, 2022
Mario + Rabbids Kingdom Battle Is The Next Switch Online Trial (Europe)

Mario + Rabbids Kingdom Battle Is The Next Switch Online Trial (Europe)

July 5, 2022
NieR:Automata For Switch Handled By Nintendo Port Specialist Virtuos

NieR:Automata For Switch Handled By Nintendo Port Specialist Virtuos

July 5, 2022
Here’s what Stage Manager looks like on the iPad mini

Here’s what Stage Manager looks like on the iPad mini

July 5, 2022
Moto G42 launches in India

Moto G42 launches in India

July 5, 2022
PlatinumGames’ Shmup Sol Cresta Receives An Update, Here’s What’s Included

PlatinumGames’ Shmup Sol Cresta Receives An Update, Here’s What’s Included

July 5, 2022
Xbox sale round-up May 10th, 2022

Xbox sale round-up July 5th, 2022

July 5, 2022
How to pick a location with stunning light for family photos

How to pick a location with stunning light for family photos

July 5, 2022
Meta to discontinue ‘Novi’ cryptocurrency digital wallet

Meta to discontinue ‘Novi’ cryptocurrency digital wallet

July 5, 2022
Samsung Galaxy S22 Ultra is expected to outsell the last four Galaxy Notes

Samsung Galaxy S22 Ultra is expected to outsell the last four Galaxy Notes

July 5, 2022
GamerCoins Beta Launches, Hundreds Of Free Game Codes Up Now!

GamerCoins Beta Launches, Hundreds Of Free Game Codes Up Now!

July 5, 2022
Five top-tier cameras that are NSFW (Not Safe For Wallets)

Five top-tier cameras that are NSFW (Not Safe For Wallets)

July 5, 2022
How to change Apple ID

How to change Apple ID

July 5, 2022
Advertise with us
Tuesday, July 5, 2022
Bookmarks
  • Login
  • Register
GetUpdated
  • Home
  • Game Updates
    • Mobile Gaming
    • Playstation News
    • Xbox News
    • Switch News
    • MMORPG
    • Game News
    • IGN
    • Retro Gaming
  • Tech News
    • Apple Updates
    • Jailbreak News
    • Mobile News
  • Software Development
  • Photography
  • Contact
    • Advertise With Us
    • About
No Result
View All Result
GetUpdated
No Result
View All Result
GetUpdated
No Result
View All Result
ADVERTISEMENT

Building Two-Factor Authentication With NestJS and Postgres

January 29, 2022
in Software Development
Reading Time:23 mins read
0 0
0
Share on FacebookShare on WhatsAppShare on Twitter


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) code.

This tutorial will teach you how to implement 2FA authentication in a NestJS application. Grab the code from Github 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 – Nodejs is the runtime environment for our application.
  • Postgres database – We’ll save the user’s records in a Postgres database.
  • Arctype – 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:

Then, create a Nest application with the command below.

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.

Setting up Postgres database - Snapshot 1

sudo su - postgres
psql
create database  authentication

Setting up Postgres database - Snapshot 2

Setting up Postgres database - Snapshot 3

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 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 the 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 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 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" <[email protected]>',
     },
     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("https://dzone.com/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("https://dzone.com/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 and static files director to enable 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="http://dzone.com/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="http://dzone.com/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 the 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 = "https://dzone.com/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 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.

Enable LSAA on your Gmail account - Snapshot 1

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

Enable LSAA on your Gmail account - Snapshot 2

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

Enable LSAA on your Gmail account - Snapshot 3

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/ to view the index page. You should see the result shown below.

Testing Application - Snapshot 1

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.

Testing Application - Snapshot 2

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

Got stuck? Have any issues? The code for this tutorial is fully available on Github 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 and take things even further.



Source link

ShareSendTweet
Previous Post

Down With Cookie Walls, Give Us Web Privacy API

Next Post

AGAR AAPKA GOOGLE SIGN DELETE HO JATA HA TO 👍VAPAS KASA LAY 🔥BY Teckfex🔥👍

Related Posts

Understanding OAuth 2.0 – DZone Security

July 4, 2022
0
0
Understanding OAuth 2.0 – DZone Security
Software Development

In a traditional client-server authentication model, a resource owner shares their credentials with the client so that the client can...

Read more

NativeScript vs. Flutter: A Comparison

July 4, 2022
0
0
NativeScript vs. Flutter: A Comparison
Software Development

With the growing demand for lifestyle and communication apps, mobile app development has become a booming industry. Building apps for...

Read more
Next Post
AGAR AAPKA  GOOGLE SIGN DELETE HO JATA HA TO 👍VAPAS KASA LAY 🔥BY Teckfex🔥👍

AGAR AAPKA GOOGLE SIGN DELETE HO JATA HA TO 👍VAPAS KASA LAY 🔥BY Teckfex🔥👍

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

© 2021 GetUpdated – MW.

  • About
  • Advertise
  • Privacy & Policy
  • Terms & Conditions
  • Contact

No Result
View All Result
  • Home
  • Game Updates
    • Mobile Gaming
    • Playstation News
    • Xbox News
    • Switch News
    • MMORPG
    • Game News
    • IGN
    • Retro Gaming
  • Tech News
    • Apple Updates
    • Jailbreak News
    • Mobile News
  • Software Development
  • Photography
  • Contact
    • Advertise With Us
    • About

Welcome Back!

Login to your account below

Forgotten Password? Sign Up

Create New Account!

Fill the forms bellow to register

All fields are required. Log In

Retrieve your password

Please enter your username or email address to reset your password.

Log In
Are you sure want to unlock this post?
Unlock left : 0
Are you sure want to cancel subscription?