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 ➜
    Xây dựng hệ thống streaming video

    Xây dựng hệ thống streaming video

    Xin chào các bạn, hôm nay mình sẽ chia sẻ với các bạn về kinh nghiệm xây dựng hệ thống streaming video và cách tích hợp với các dịch vụ của bên thứ ba như Dropbox và Bunny. Qua bài viết này, mình hy vọng sẽ cung cấp cho các bạn một cái nhìn tổng quan về kiến trúc streaming video và những lựa chọn phổ biến khi làm việc với các dịch vụ bên ngoài.

    Backend
    Tutorials
    Frontend
    Beiryu

    Beiryu

    Contributor

    2
    The rollercoaster ride of landing a programming job as a student

    The rollercoaster ride of landing a programming job as a student

    Hey there, programming students! Job hunting can be a real grind, right? But don't sweat it, if you go about it the right way, you can totally ace it and land a cool gig at a groovy programming company. Just do your homework about the company, spruce up that CV and portfolio of yours, and practice your interview skills. Remember, every step counts towards reaching that dream job of yours.

    Career
    Tutorials
    Beiryu

    Beiryu

    Contributor

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