2FA in NestJS Application & ReactJS using Google Authenticator
October 23, 2023
2FA in NestJS Application & ReactJS using Google Authenticator
This document provides a step-by-step guide on implementing two-factor authentication (2FA) in a NestJS application with ReactJS using Google Authenticator. It covers initializing the NestJS project with basic login password authentication, creating the authentication module, implementing local authentication strategy, adding JWT management, and integrating 2FA. The document includes code snippets and instructions for each step.

I recently had to implement a two factor authentication on a project for my company and it was a whole new thing for me. Sure I had already used 2fa before but I had never implemented it.

What is 2fa ? Well we all know that passwords aren't really secure enough to avoid security breaches so...

2FA is an extra layer of security used to make sure that people trying to gain access to an online account are who they say they are. First, a user will enter their username and a password. Then, instead of immediately gaining access, they will be required to provide another piece of information.

In this case, I was asked to use the google authenticator app to generate a 2fa code that would be used to authenticate the user after the login step.

Two factor authentication

Here's the login flow for 2fa authentication:

  • The user logs in with his email and password
  • If the 2fa is not enabled, he can enable it using the turn-on route. This will generate a QrCode that the user will scan with the google authenticator app.
  • The use then uses the random code the app has generated to authenticate
  • Creating the 2fa system

    First we have to create a unique secret for every user that turns on 2fa, but we'll also need a special otp authentication url that we'll be using later to create a QrCode. The otplib package is a good match, so let's install it.

    yarn add otplib

    We should also update the user entity.

    typescript
    export class User {
    @Exclude()
    @Column({ type: DataType.STRING })
    twoFactorAuthenticationSecret: string;
    @Exclude()
    @Column({ type: DataType.STRING })
    twoFactorAuthenticationBackupCode: string;
    }

    Then we create a method to generate the secret, backupCode and otpAuthUrl in the authentication service and return them. The TWO_FA_APP_NAME is the name that will appear in the google authenticator app.

    typescript
    async generateTwoFactorAuthenticationSecret(user: UserEntity) {
    const secret = authenticator.generateSecret();
    const backupCode = StringUtils.random();
    const otpAuthUrl = authenticator.keyuri(
    user.email,
    configParams.TWO_FA_APP_NAME,
    secret,
    );
    await this.userService.set2FASecretAndBackupCode(
    secret,
    backupCode,
    +user.id,
    );
    return {
    secret,
    backupCode,
    otpAuthUrl,
    };
    }

    We have to update the user with the secret and backupCode that has just been generated. Once again, this should all be in a database.

    typescript
    async set2FASecretAndBackupCode(
    secret: string,
    backupCode: string,
    userId: number,
    ) {
    const user = await this.userRepository.findOne({ where: { id: userId } });
    if (!user) {
    customThrowError('User not found', HttpStatus.BAD_REQUEST);
    }
    await this.userRepository.update(
    {
    twoFactorAuthenticationSecret: secret,
    twoFactorAuthenticationBackupCode: backupCode,
    },
    { where: { id: userId } },
    );
    }

    Now, we can generate the QrCode that will be used to add our application to the google authenticator app.

    yarn add qrcode

    Let's add the generate method in the authentication service.

    typescript
    import { toDataURL } from 'qrcode';
    async generateQrCodeDataURL(otpAuthUrl: string) {
    return toDataURL(otpAuthUrl);
    }

    Now we need to offer the possiblity for the user to turn on the 2fa. So let's add another property to the user interface.

    typescript
    export class User {
    @Column({ type: DataType.BOOLEAN, defaultValue: false })
    is2FAEnabled: boolean;
    }

    Add the switch method in the users service

    typescript
    async switchTwoFactorAuthentication(userId: number, status: boolean) {
    const user = await this.userRepository.findOne({ where: { id: userId } });
    if (!user) {
    customThrowError('User not found', HttpStatus.BAD_REQUEST);
    }
    await this.userRepository.update(
    { is2FAEnabled: status },
    { where: { id: userId } },
    );
    }

    Add the method that will verify the authentication code with the user's secret

    typescript
    async authenticateWithTwoFactor(code: string, user: UserEntity) {
    const isCodeValid = await this.isTwoFactorAuthenticationCodeValid(
    code,
    user,
    );
    if (!isCodeValid) {
    customThrowError('Wrong authentication code', 401);
    }
    const accessToken = await this.authService.createSignInToken({
    id: user.id,
    email: user.email,
    isSecondFactorAuthenticated: user.is2FAEnabled,
    });
    return { accessToken, user };
    }
    Testing

    So first we'll do a POST request to log in with the user and password:

    A visual depiction of what is being written about

    This will return the following:

    A visual depiction of what is being written about

    Then, we need to get the QrCode to add our app to the google authenticator app

    A visual depiction of what is being written about

    This will return a base64 data url which in turn will happen to be a QrCode

    A visual depiction of what is being written about

    If you scan this with the Google Authenticator App it should add your app:

    A visual depiction of what is being written about

    And then you should be able to call the authenticate route with the current code from the google authenticator app

    Discussion (0)

    Loading...

    Recommended articles

    More articles ➜
    Caching Data from CMS in Next.js

    Caching Data from CMS in Next.js

    Hey there! This piece will show you how to supercharge your page loading on NextJs with data caching. It's pretty cool, you can use

    Frontend
    Caching
    Tutorials
    Beiryu

    Beiryu

    Contributor

    0
    The Misunderstood Nature of Stress: A Perspective

    The Misunderstood Nature of Stress: A Perspective

    Stress is not an inherent part of our lives, but a result of our inability to manage our minds, emotions, bodies, and energies effectively. It's not external situations, but our internal mechanisms that cause stress. A daily routine called 'inner engineering' can help reduce stress significantly. By understanding and managing ourselves better, we can avoid stress altogether.

    Side hustle
    Beiryu

    Beiryu

    Contributor

    0
    Subscribe to the newsletter
    Get emails from me about web development, tech, and early access to new articles.