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!
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.
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.
Now, let’s install the dependencies for this project. We’ll start with the dev dependencies using the command below.
Then we’ll add our other dependencies.
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. You can host Postgres on Digital Ocean or other hosting providers. If you use Postgres locally make sure you know the password or follow these steps to reset the Postgres password. 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.
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.
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.
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 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.
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.
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 createthe sendConfirmationEmail and sendConfirmedEmail methods to send confirmation and verification emails to registered users.
Next, we create the signup method to handle the user’s registration. We do this with the code snippet below.
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:
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.
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.
Create a .env file in the project root directory to store your JWT secret. You can generate one using the built-in crypto module.
And store the generated secret in the .env file you created.
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.
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.
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.
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.
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.
Then we create a Verify route and await the result from the appServiceverifyAccount method, which takes the user confirmation code as a parameter.
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.
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.
Open the verify.hbs file and add the code snippet below:
Add the code snippet code below to the email-templates/confirm.hbs file.
And the code snippet below to the email-templates/confirmed.hbs file.
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:
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.
Open Chrome. Click on the profile icon on the top right-hand side of your browser.
Click on Manage your Google Account.
Type less on the search box, and click on less secure app access.
Toggle the Allow less secure apps: ON input box to enable it.
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.
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.
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.
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.
Follow Arctype's Development
Programming stories, tutorials, and database tips every 2 weeks