Angular 7, Google Authenticator,Node JS with Two-Factor Authentication

Angular 7, Google Authenticator,Node JS with Two-Factor Authentication
Create an Angular 7 + Google Authenticator + Node JS Web App with Two-Factor Authentication

“Turn on all security features like two-factor authentication. People who do that generally don’t get hacked. Don’t care? You will when you get hacked. Do the same for your email and other social services, too.” — Robert Scoble

“Extra layer of security” — who doesn’t need it? Is your website having a provision to enable Two-Factor authentication (TFA) for your users? Yes, you would definitely need your user-base to be more secure than just having a password based authentication.

In this tutorial we’ll learn how to easily enable and integrate the 2-Factor Authentication in an Angular-7 app using Node JS as the back-end technology along with Google Authenticator, that provides Time based — One Time Password(TOTP). By the end of this post, you will be able to create an application that has a simple login and registration feature along with the 2-Factor Authentication.

GitHub repository to be followed: angular-node-mfauth (https://github.com/Narendra-Kamath/angular-node-mfauth)

Application Demo Video

Prerequisites

  1. Node JS (LTS) — [Download] (using v10.15.3 LTS in the tutorial)
  2. Google Authenticator — [Download: Android] [Download: iOS]

After having the above mentioned tools being installed, the next step would be to create the API services for the application.

Step 1: Server-side Application

For creating the API services, we would be using the minimal and flexible web framework for Node.js called as Express.js. Let us now create a dedicated directory ‘back-end’ for our server-side app and navigate into that folder in the terminal/ command prompt and install the required project dependencies.

> mkdir back-end
> cd back-end
> npm init -y
> npm install --save express body-parser cors qrcode speakeasy

Now, we have created a directory ‘back-end’ and initialized it as a Node.js project by installing the following dependencies:

  1. express — This is a minimal and flexible web framework for creating API services.
  2. body-parser — In order to parse the HTTP method’s body data, this package is being used.
  3. cors — This package is used in order to enable the client side web application to communicate with the API services and to avoid the cross-origin issue.
  4. qrcode — In this application we would be generating the QR-code as a base64 image data, and thus we require qrcode package.
  5. **speakeasy **— This is the package that enables our application to provide with the secret key and the T-OTP algorithm that the Google Authenticator uses and is also useful for the verification of the Auth code being provided.

We will now create a few API services, with app.js as the main file of execution. For the simplicity of learning process, separation of concerns is followed for the scaffolding of the application.

The API services will expose the features of login, registration and TFA with the following routes:

  1. Login service: The login service for the application would include the basic functionalities for a login using the username, password and Auth Code if TFA is enabled or else just with the username and password.
const express = require('express');
const speakeasy = require('speakeasy');
const commons = require('./commons');
const router = express.Router();

router.post('/login', (req, res) => {
    console.log(`DEBUG: Received login request`);

    if (commons.userObject.uname && commons.userObject.upass) {
        if (!commons.userObject.tfa || !commons.userObject.tfa.secret) {
            if (req.body.uname == commons.userObject.uname && req.body.upass == commons.userObject.upass) {
                console.log(`DEBUG: Login without TFA is successful`);

                return res.send({
                    "status": 200,
                    "message": "success"
                });
            }
            console.log(`ERROR: Login without TFA is not successful`);

            return res.send({
                "status": 403,
                "message": "Invalid username or password"
            });

        } else {
            if (req.body.uname != commons.userObject.uname || req.body.upass != commons.userObject.upass) {
                console.log(`ERROR: Login with TFA is not successful`);

                return res.send({
                    "status": 403,
                    "message": "Invalid username or password"
                });
            }
            if (!req.headers['x-tfa']) {
                console.log(`WARNING: Login was partial without TFA header`);

                return res.send({
                    "status": 206,
                    "message": "Please enter the Auth Code"
                });
            }
            let isVerified = speakeasy.totp.verify({
                secret: commons.userObject.tfa.secret,
                encoding: 'base32',
                token: req.headers['x-tfa']
            });

            if (isVerified) {
                console.log(`DEBUG: Login with TFA is verified to be successful`);

                return res.send({
                    "status": 200,
                    "message": "success"
                });
            } else {
                console.log(`ERROR: Invalid AUTH code`);

                return res.send({
                    "status": 206,
                    "message": "Invalid Auth Code"
                });
            }
        }
    }

    return res.send({
        "status": 404,
        "message": "Please register to login"
    });
});

module.exports = router;

login.js
In this tutorial we wouldn’t be using a Database to store the user object and hence we would be using a common and shared user object on the server side.

2. Registration service: The registration of a user in the application would be just to add the username and password to the userObject as well as to reset the already existing userObject information. Since the login and registration modules are made just for the demonstration purpose, the application will support only a single user login and registration.

let userObject = {};

module.exports = {
    userObject
};

commons.js

3. TFA service: This service is to provide a feature for the setup of the two factor authentication along with the verification of the T-OTP code generated by Google Authenticator. The service would include the functionalities to GET the existing TFA configuration as well as to enable or disable the TFA for a userObject.

const express = require('express');
const commons = require('./commons');
const router = express.Router();

router.post('/register', (req, res) => {
    console.log(`DEBUG: Received request to register user`);

    const result = req.body;

    if ((!result.uname && !result.upass) || (result.uname.trim() == "" || result.upass.trim() == "")) {
        return res.send({
            "status": 400,
            "message": "Username/ password is required"
        });
    }

    commons.userObject.uname = result.uname;
    commons.userObject.upass = result.upass;
    delete commons.userObject.tfa;

    return res.send({
        "status": 200,
        "message": "User is successfully registered"
    });
});

module.exports = router;

register.js

The above mentioned routes are exported for a common integration and single entry file in the root directory ‘app.js.’ This would start the express generated HTTP server on the localhost with port number 3000.

const express = require('express');
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');
const commons = require('./commons');
const router = express.Router();

router.post('/tfa/setup', (req, res) => {
    console.log(`DEBUG: Received TFA setup request`);

    const secret = speakeasy.generateSecret({
        length: 10,
        name: commons.userObject.uname,
        issuer: 'NarenAuth v0.0'
    });
    var url = speakeasy.otpauthURL({
        secret: secret.base32,
        label: commons.userObject.uname,
        issuer: 'NarenAuth v0.0',
        encoding: 'base32'
    });
    QRCode.toDataURL(url, (err, dataURL) => {
        commons.userObject.tfa = {
            secret: '',
            tempSecret: secret.base32,
            dataURL,
            tfaURL: url
        };
        return res.json({
            message: 'TFA Auth needs to be verified',
            tempSecret: secret.base32,
            dataURL,
            tfaURL: secret.otpauth_url
        });
    });
});

router.get('/tfa/setup', (req, res) => {
    console.log(`DEBUG: Received FETCH TFA request`);

    res.json(commons.userObject.tfa ? commons.userObject.tfa : null);
});

router.delete('/tfa/setup', (req, res) => {
    console.log(`DEBUG: Received DELETE TFA request`);

    delete commons.userObject.tfa;
    res.send({
        "status": 200,
        "message": "success"
    });
});

router.post('/tfa/verify', (req, res) => {
    console.log(`DEBUG: Received TFA Verify request`);

    let isVerified = speakeasy.totp.verify({
        secret: commons.userObject.tfa.tempSecret,
        encoding: 'base32',
        token: req.body.token
    });

    if (isVerified) {
        console.log(`DEBUG: TFA is verified to be enabled`);

        commons.userObject.tfa.secret = commons.userObject.tfa.tempSecret;
        return res.send({
            "status": 200,
            "message": "Two-factor Auth is enabled successfully"
        });
    }

    console.log(`ERROR: TFA is verified to be wrong`);

    return res.send({
        "status": 403,
        "message": "Invalid Auth Code, verification failed. Please verify the system Date and Time"
    });
});

module.exports = router;

tfa.js

Thus we have setup the server side code for our web application. The server script could be started and let it Rest In its Place ;)

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cors = require('cors');
const login = require('./routes/login');
const register = require('./routes/register');
const tfa = require('./routes/tfa');

app.use(bodyParser.json());
app.use(cors());

app.use(login);
app.use(register);
app.use(tfa);

app.listen('3000', () => {
    console.log('The server started running on http://localhost:3000');
});

app.js
Thus we have setup the server side code for our web application. The server script could be started and let it Rest In its Place ;)

Now the next step would be to create a simple Angular 7 application to consume these created services.

Step 2: Angular 7 application

For creating an Angular 7 application, we should first install Angular globally. After installing angular, we’ll create an app by the name ‘front-end’ and we’ll also install the local dependency of ‘bootstrap’ (link the bootstrap.min.css in styles.css) by navigating to front-end directory.

> npm install -g @angular/cli
> ng new front-end
> cd front-end
> npm install --save bootstrap
> ng serve

After successfully creating the angular app and launching the app server, we’ll generate a few components, guards and services required for the application.

For the purpose of demonstration we would be creating a LoginService and two guards — ‘Auth Guard’ and ‘Login state management Guard.’

> ng g s services/login-service/login-service --spec=false
> ng g g guards/AuthGuard
> ng g g guards/Login

The guards generated here are the CanActivate guards. The login-service would include the HTTP calls to the services created at back-end.

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Subject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class LoginServiceService {
  headerOptions: any = null

  _isLoggedIn: boolean = false

  authSub = new Subject<any>();

  constructor(private _http: HttpClient) {
  }

  loginAuth(userObj: any) {
    if (userObj.authcode) {
      console.log('Appending headers');
      this.headerOptions = new HttpHeaders({
        'x-tfa': userObj.authcode
      });
    }
    return this._http.post("http://localhost:3000/login", { uname: userObj.uname, upass: userObj.upass }, { observe: 'response', headers: this.headerOptions });
  }

  setupAuth() {
    return this._http.post("http://localhost:3000/tfa/setup", {}, { observe: 'response' })
  }

  registerUser(userObj: any) {
    return this._http.post("http://localhost:3000/register", { uname: userObj.uname, upass: userObj.upass }, { observe: "response" });
  }

  updateAuthStatus(value: boolean) {
    this._isLoggedIn = value
    this.authSub.next(this._isLoggedIn);
    localStorage.setItem('isLoggedIn', value ? "true" : "false");
  }

  getAuthStatus() {
    this._isLoggedIn = localStorage.getItem('isLoggedIn') == "true" ? true : false;
    return this._isLoggedIn
  }

  logoutUser() {
    this._isLoggedIn = false;
    this.authSub.next(this._isLoggedIn);
    localStorage.setItem('isLoggedIn', "false")
  }

  getAuth() {
    return this._http.get("http://localhost:3000/tfa/setup", { observe: 'response' });
  }

  deleteAuth() {
    return this._http.delete("http://localhost:3000/tfa/setup", { observe: 'response' });
  }

  verifyAuth(token: any) {
    return this._http.post("http://localhost:3000/tfa/verify", { token }, { observe: 'response' });
  }
}

login-service.service.ts

The AuthGuard would restrict the user to navigate to the Home page without login.

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { LoginServiceService } from 'src/app/services/login-service/login-service.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuardGuard implements CanActivate {

  constructor(private _loginService: LoginServiceService, private _router: Router) {
  }
  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    if (this._loginService.getAuthStatus()) {
      return true;
    }

    this._router.navigate(['/login'])

    return false;
  }

}

auth-guard.guard.ts

The login-guard would not allow the user to navigate to login or registration page if the user is already logged-in.

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { LoginServiceService } from 'src/app/services/login-service/login-service.service';

@Injectable({
  providedIn: 'root'
})
export class LoginGuard implements CanActivate {
  constructor(private _loginService: LoginServiceService, private _router: Router) {
  }
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot) {
    if (!this._loginService.getAuthStatus()) {
      return true;
    }

    this._router.navigate(['/home'])
    return false;
  }

}

login.guard.ts

Since we have completed with the backbone for our application by creating the services and guards, we’d now create a few major components and also configure the routing for the application.

> ng g c components/header --spec=false
> ng g c components/home --spec=false
> ng g c components/login --spec=false
> ng g c components/register --spec=false

After creating the necessary components for the app, we’ll now configure the routing for the app, by linking the respective guards for activating the routes.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './components/home/home.component';
import { LoginComponent } from './components/login/login.component';
import { RegisterComponent } from './components/register/register.component';
import { AuthGuardGuard } from './guards/AuthGuard/auth-guard.guard';
import { LoginGuard } from './guards/Login/login.guard';

const routes: Routes = [
  { path: "", redirectTo: '/login', pathMatch: 'full', canActivate: [LoginGuard] },
  { path: "login", component: LoginComponent, canActivate: [LoginGuard] },
  { path: "home", component: HomeComponent, canActivate: [AuthGuardGuard] },
  { path: "register", component: RegisterComponent, canActivate: [LoginGuard] },
  { path: "**", redirectTo: '/login', pathMatch: 'full', canActivate: [LoginGuard] }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

app-routing.module.ts

Let’s now have some code done on the components created, before which we have to remove the default code in app.component.html, and we’ll just mention the common header component and the router-outlet.

<app-header></app-header>
<router-outlet></router-outlet>

app.component.html

  1. Header Component: This is a common component for the other components, that includes a navigation bar for the application. The visibility of the links in the header are controlled by the getAuthStatus() of LoginService.
<nav class="navbar navbar-expand-sm navbar-dark bg-dark" style="z-index: 99999;">
  <a class="navbar-brand" [routerLink]="['/login']">NarenAuth v0.0</a>
  <button class="navbar-toggler d-lg-none" type="button" data-toggle="collapse" data-target="#collapsibleNavId"
    aria-controls="collapsibleNavId" aria-expanded="false" aria-label="Toggle navigation" (click)="toggleMenuBar()">
    <span class="navbar-toggler-icon"></span>
  </button>
  <div class="collapse navbar-collapse" id="collapsibleNavId">
    <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
      <li class="nav-item" [routerLinkActive]="['active']" *ngIf="!isLoggedIn">
        <a class="nav-link" [routerLink]="['/login']">Login</a>
      </li>
      <li class="nav-item" [routerLinkActive]="['active']" *ngIf="!isLoggedIn">
        <a class="nav-link" [routerLink]="['/register']">Register</a>
      </li>
    </ul>
    <ul class="navbar-nav ml-auto">
      <li class="nav-item" *ngIf="isLoggedIn">
        <a class="nav-link" (click)="logout()">Logout</a>
      </li>
    </ul>
  </div>
</nav>

header.component.html

In the background of this HTML, we would require the *.ts file as well, for the header component.

import { Component, OnInit } from '@angular/core';
import { LoginServiceService } from 'src/app/services/login-service/login-service.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
  isLoggedIn: boolean = false
  constructor(private _loginService: LoginServiceService, private _router: Router) {
    this._loginService.authSub.subscribe((data) => {
      this.isLoggedIn = data
    })
  }

  ngOnInit() {
    this.isLoggedIn = this._loginService.getAuthStatus()
  }

  toggleMenuBar() {
    if(document.getElementById("collapsibleNavId").style.display == "block") {
      document.getElementById("collapsibleNavId").style.display = "none";
    } else {
      document.getElementById("collapsibleNavId").style.display = "block";
    }
  }

  logout() {
    this._loginService.logoutUser()
    this._router.navigate(['/login'])
  }
}

header.component.ts

2. Login Component: This is a simple component to accept the username, password and the AuthCode (if TFA is enabled) from the user and to verify it with the back-end services. If the user information is valid, then the user will be navigated to the HomeComponent.

<div class="container">
  <div class="card card-container">
    <img id="profile-img" class="profile-img-card" src="assets/images/avatar_2x.png" />
    <form class="form-signin" (ngSubmit)="loginUser()" #loginForm="ngForm">
      <input type="text" id="uname" class="form-control" name="uname" autocomplete="off" #uname="ngModel"
        [(ngModel)]="userObject.uname" placeholder="Username" title="Please enter the username" required autofocus>
      <input type="password" id="upass" class="form-control" name="upass" autocomplete="off" #upass="ngModel"
        [(ngModel)]="userObject.upass" placeholder="Password" title="Please enter the password" required>
      <input type="text" id="authcode" class="form-control" *ngIf="this.tfaFlag" name="authcode" autocomplete="off"
        #authcode="ngModel" [(ngModel)]="userObject.authcode" placeholder="Two-Factor Auth code"
        title="Please enter the code" required>
      <button class="btn btn-lg btn-primary btn-block btn-signin" type="submit"
        [disabled]="uname?.errors?.required || upass?.errors?.required || (this.tfaFlag && authcode?.errors?.required)">Sign
        in</button>
      <p style="text-align:center;">Want to reset login? <a [routerLink]="['/register']">Register
          here</a></p>
      <p class="text-danger" style="text-align:center;" *ngIf="errorMessage">{{errorMessage}}</p>
    </form>
  </div>
</div>

login.component.html

We would also be verifying the status received from the back-end to display appropriate messages to the user.

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router'
import { LoginServiceService } from 'src/app/services/login-service/login-service.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  tfaFlag: boolean = false
  userObject = {
    uname: "",
    upass: ""
  }
  errorMessage: string = null
  constructor(private _loginService: LoginServiceService, private _router: Router) {
  }

  ngOnInit() {
  }

  loginUser() {
    this._loginService.loginAuth(this.userObject).subscribe((data) => {
      this.errorMessage = null;
      if (data.body['status'] === 200) {
        this._loginService.updateAuthStatus(true);
        this._router.navigateByUrl('/home');
      }
      if (data.body['status'] === 206) {
        this.tfaFlag = true;
      }
      if (data.body['status'] === 403) {
        this.errorMessage = data.body['message'];
      }
      if (data.body['status'] === 404) {
        this.errorMessage = data.body['message'];
      }
    })
  }
}

login.component.ts

3. Registration Component: As stated earlier in this post, we would be able to register a single user in the whole application and we would require the user to enter any username and password (certainly the one that you would remember :P ) for the registration purpose.

<div class="container">
  <div class="card card-container">
    <img id="profile-img" class="profile-img-card" src="assets/images/avatar_2x.png" />
    <form class="form-signin" (ngSubmit)="registerUser()" #registerForm="ngForm">
      <input type="text" id="uname" class="form-control" name="uname" #uname="ngModel" [(ngModel)]="userObject.uname"
        placeholder="Username" title="Please enter the username" autocomplete="off" required autofocus>
      <input type="password" id="upass" class="form-control" name="upass" placeholder="Password"
        title="Please enter the password" #upass="ngModel" autocomplete="off" [(ngModel)]="userObject.upass" required>
      <input type="password" id="confirmpass" class="form-control" name="confirmpass" placeholder="Confirm password"
        title="Please re-enter the password" #uconfirmpass="ngModel" autocomplete="off" [(ngModel)]="confirmPass"
        required>
      <button class="btn btn-lg btn-primary btn-block btn-signin" type="submit"
        [disabled]="(uname?.errors?.required || upass?.errors?.required || uconfirmpass?.errors?.required) || (upass.value !== uconfirmpass.value)">Sign
        up</button>
        <p style="text-align:center;">Remember credentials? <a [routerLink]="['/login']">Login
          here</a></p>
      <p class="text-success" style="text-align:center;" *ngIf="errorMessage">{{errorMessage}}</p>
    </form>
  </div>
</div>

register.component.html

In case you forget your username or the password provided for the application or in case if you miss the TFA secret key in your device, then simply provide a new username and password in the registration page (basically you would be resetting the userObject :P).

import { Component, OnInit } from '@angular/core';
import { LoginServiceService } from 'src/app/services/login-service/login-service.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.css']
})
export class RegisterComponent implements OnInit {
  errorMessage: string = null
  userObject = {
    uname: "",
    upass: ""
  }
  confirmPass: string = ""

  constructor(private _loginService: LoginServiceService, private _router: Router) { }

  ngOnInit() {
  }

  registerUser() {
    if (this.userObject.uname.trim() !== "" && this.userObject.upass.trim() !== "" && (this.userObject.upass.trim() === this.confirmPass))
      this._loginService.registerUser(this.userObject).subscribe((data) => {
        const result = data.body
        if (result['status'] === 200) {
          this.errorMessage = result['message'];
          setTimeout(() => {
            this._router.navigate(['/login']);
          }, 2000);
        }
      });
  }
}

register.component.ts

Once the user is registered and logged in with the username and password, the user will be provided with an option to enable or disable the Two-Factor Authentication in the HomeComponent.

4. Home Component: In this component, we would be allowing user to setup and verify the TFA. As soon as you land on this page, there will be an option to setup the TFA, where the QR-Code, which is to be scanned in the Google Authenticator app. As soon as you scan, the T-OTP (TFA element) linked with the userObject will be included in the Google Authenticator app. The AuthCode will be displayed on time basis in the app and the same code should be entered in order to verify and enable TFA for the userObject.

<div class="container">
  <div class="card card-container">
    <div *ngIf="this.tfa.secret">

      <h5 style="border-bottom: 1px solid #a8a8a8; padding-bottom: 5px;">Current Settings</h5>

      <img [src]="tfa.dataURL" alt="" class="img-thumbnail" style="display:block;margin:auto">

      <p>Secret Key - {{tfa.secret || tfa.tempSecret}}</p>

      <p>Auth Type - Time Based - OTP</p>

      <button class="btn btn-lg btn-danger btn-block btn-signin" (click)="disabledTfa()">Disable TFA</button>
    </div>
    <div *ngIf="!tfa.secret">

      <h5 style="border-bottom: 1px solid #a8a8a8; padding-bottom: 5px;">Setup TFA</h5>

      <span *ngIf="!!tfa.tempSecret">

        <p>Scan the QR code or enter the secret key in Google Authenticator</p>

        <img [src]="tfa.dataURL" alt="" class="img-thumbnail" style="display:block;margin:auto">

        <p>Secret Key - {{tfa.tempSecret}}</p>

        <p>Auth Type - Time Based - OTP</p>

        <form class="form-group" (ngSubmit)="confirm()" #otpForm="ngForm">
          <input name="authcode" type="number" #iauthcode="ngModel" class="form-control" maxlength="6"
            placeholder="Enter the Auth Code" id="authcode" autocomplete="off" [(ngModel)]="authcode" required>
          <br>
          <button type="Submit" class="btn btn-lg btn-primary btn-block btn-signin"
            [disabled]="iauthcode?.errors?.required">Enable TFA</button>
        </form>
        <p class="text-danger" style="text-align:center;" *ngIf="errorMessage">{{errorMessage}}</p>
      </span>
    </div>
  </div>
</div>

home.component.html

If the user has enabled TFA, then the current settings with the QR-Code and Secret Key will be displayed, along with an option to disable the TFA linked with userObject.

import { Component, OnInit } from '@angular/core';
import { LoginServiceService } from 'src/app/services/login-service/login-service.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

  tfa: any = {};
  authcode: string = "";
  errorMessage: string = null;

  constructor(private _loginService: LoginServiceService) {
    this.getAuthDetails();
  }

  ngOnInit() {
  }

  getAuthDetails() {
    this._loginService.getAuth().subscribe((data) => {
      const result = data.body
      if (data['status'] === 200) {
        console.log(result);
        if (result == null) {
          this.setup();
        } else {
          this.tfa = result;
        }
      }
    });
  }

  setup() {
    this._loginService.setupAuth().subscribe((data) => {
      const result = data.body
      if (data['status'] === 200) {
        console.log(result);
        this.tfa = result;
      }
    });
  }

  confirm() {
    this._loginService.verifyAuth(this.authcode).subscribe((data) => {
      const result = data.body
      if (result['status'] === 200) {
        console.log(result);
        this.errorMessage = null;
        this.tfa.secret = this.tfa.tempSecret;
        this.tfa.tempSecret = "";
      } else {
        this.errorMessage = result['message'];
      }
    });
  }

  disabledTfa() {
    this._loginService.deleteAuth().subscribe((data) => {
      const result = data.body
      if (data['status'] === 200) {
        console.log(result);
        this.authcode = "";
        this.getAuthDetails();
      }
    });
  }

}

home.component.ts

Hurray!! If you have reached till this length of the post, then you have successfully learnt of how to easily integrate the Two-Factor Authentication in your Angular 7 application. For any debugging process, you may look at the console of either the front-end application or the back-end application. Hope it was easy to integrate Two-Factor Authentication with your Angular 7 app… Cheers!!

Kindly share your views in the responses section :) Thank you!

Angular (Full App) with Angular Material, Angularfire & NgRx

Angular 7 with Angular Material and Firebase Cloud Firestore

Angular 6 , Angular 7 Step by Step for beginners

Angular 5 - A 3-Step Process to Master Angular for Beginners

Angular 6 (Angular 2+) & React 16 - The Complete App Guide

Suggest:

Angular Tutorial - Learn Angular from Scratch

Angular and Nodejs Integration Tutorial

Learn Angular 8 from Scratch for Beginners - Crash Course

React Passwordless Authentication Tutorial | React, NodeJS, Stytch API

Test Driven Development with Angular

Intro to HTML & CSS - Tutorial