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

Angular 8 Node & Express JS File Upload

Angular 8 Node & Express JS File Upload

In this Angular 8 and Node.js tutorial, we are going to look at how to upload files on the Node server. To create Angular image upload component, we will be using Angular 8 front-end framework along with ng2-file-upload NPM package; It’s an easy to use Angular directives for uploading the files.

In this Angular 8 and Node.js tutorial, we are going to look at how to upload files on the Node server. To create Angular image upload component, we will be using Angular 8 front-end framework along with ng2-file-upload NPM package; It’s an easy to use Angular directives for uploading the files.

We are also going to take the help of Node.js to create the backend server for Image or File uploading demo. Initially, we’ll set up an Angular 8 web app from scratch using Angular CLI. You must have Node.js and Angular CLI installed in your system.

We’ll create the local server using Node.js and multer middleware. Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files. Once we are done setting up front-end and backend for our File uploading demo then, we’ll understand step by step how to configure file uploading in Angular 8 app using Node server.

Prerequisite

In order to show you Angular 8 File upload demo, you must have Node.js and Angular CLI installed in your system. If not then check out this tutorial: Set up Node JS

Run following command to install Angular CLI:

npm install @angular/cli -g

Install Angular 8 App

Run command to install Angular 8 project:

ng new angular-node-file-upload

# ? Would you like to add Angular routing? No
# ? Which stylesheet format would you like to use? CSS
cd angular-node-file-upload

Show Alert Messages When File Uploaded

We are going to install and configure ngx-toastr an NPM package which helps in showing the alert message when the file is uploaded on the node server.

npm install ngx-toastr --save

The ngx-toastr NPM module requires @angular/animations dependency:

npm install @angular/animations --save

Then, add the ngx-toastr CSS in angular.json file:

"styles": [
    "src/styles.css",
    "node_modules/ngx-toastr/toastr.css"
]

Import BrowserAnimationsModule and ToastrModule in app.module.ts file:

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';
 
@NgModule({
  imports: [
    CommonModule,
    BrowserAnimationsModule, // required animations module
    ToastrModule.forRoot() // ToastrModule added
  ]
})

export class AppModule { }

Install & Configure ng-file-upload Directive

In this step, we’ll Install and configure ng-file-upload library in Angular 8 app. Run command to install ng-file-upload library.

npm install ng2-file-upload

Once the ng2-file-upload directive is installed, then import the FileSelectDirective and FormsModule in app.module.ts. We need FormsModule service so that we can create the file uploading component in Angular.

import { FileSelectDirective } from 'ng2-file-upload';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    FileSelectDirective
  ],
  imports: [
    FormsModule
  ]
})

export class AppModule { }

Setting Up Node Backend for File Upload Demo

To upload the file on the server, we need to set up a separate backend. In this tutorial, we will be using Node & Express js to create server locally along with multer, express js, body-parser, and dotenv libraries.

Run command to create backend folder in Angular app’s root directory:

mkdir backend && cd backend

In the next step, create a specific package.json file.

npm init

Run command to install required dependencies:

npm install express cors body-parser multer dotenv --save

In order to get rid from starting the server again and again, install nodemon NPM package. Use –-save-dev along with the npm command to register in the devDependencies array. It will make it available for development purpose only.

npm install nodemon --save-dev

Have a look at final pacakge.json file for file upload demo backend:

{
  "name": "angular-node-file-upload",
  "version": "1.0.0",
  "description": "Angualr 8 file upload demo app",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "Digamber Rawat",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "dotenv": "^8.0.0",
    "express": "^4.17.1",
    "multer": "^1.4.1"
  },
  "devDependencies": {
    "nodemon": "^1.19.1"
  }
}

Create a file by the name of server.js inside backend folder:

Configure Server.js

To configure our backend we need to create a server.js file. In this file we’ll keep our backend server’s settings.

touch server.js

Now, paste the following code in backend > server.js file:

const express = require('express'),
  path = require('path'),
  cors = require('cors'),
  multer = require('multer'),
  bodyParser = require('body-parser');

// File upload settings  
const PATH = './uploads';

let storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, PATH);
  },
  filename: (req, file, cb) => {
    cb(null, file.fieldname + '-' + Date.now())
  }
});

let upload = multer({
  storage: storage
});

// Express settings
const app = express();
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));

app.get('/api', function (req, res) {
  res.end('File catcher');
});

// POST File
app.post('/api/upload', upload.single('image'), function (req, res) {
  if (!req.file) {
    console.log("No file is available!");
    return res.send({
      success: false
    });

  } else {
    console.log('File is available!');
    return res.send({
      success: true
    })
  }
});

// Create PORT
const PORT = process.env.PORT || 8080;
const server = app.listen(PORT, () => {
  console.log('Connected to port ' + PORT)
})

// Find 404 and hand over to error handler
app.use((req, res, next) => {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  console.error(err.message);
  if (!err.statusCode) err.statusCode = 500;
  res.status(err.statusCode).send(err.message);
});

Now, while staying in the backend folder run the below command to start the backend server:

nodemon server.js

If everything goes fine then you’ll get the following output:

[nodemon] 1.19.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node server.js`
Connected to port 8080

Create Angular 8 File Upload Component

In this last step, we are going to create a file upload component in Angular 8 app using Express js API.

Get into the app.component.ts file and include the following code:

import { Component, OnInit } from '@angular/core';
import { FileUploader } from 'ng2-file-upload/ng2-file-upload';
import { ToastrService } from 'ngx-toastr';

const URL = 'http://localhost:8080/api/upload';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {
  public uploader: FileUploader = new FileUploader({
    url: URL,
    itemAlias: 'image'
  });

  constructor(private toastr: ToastrService) { }

  ngOnInit() {
    this.uploader.onAfterAddingFile = (file) => {
      file.withCredentials = false;
    };
    this.uploader.onCompleteItem = (item: any, status: any) => {
      console.log('Uploaded File Details:', item);
      this.toastr.success('File successfully uploaded!');
    };
  }

}

Go to app.component.html file and add the given below code:

<div class="wrapper">
  <h2>Angular Image Upload Demo</h2>

  <div class="file-upload">
    <input type="file" name="image" ng2FileSelect [uploader]="uploader" accept="image/x-png,image/gif,image/jpeg" />
    <button type="button" (click)="uploader.uploadAll()" [disabled]="!uploader.getNotUploadedItems().length">
      Upload
    </button>
  </div>

</div>

Now, It’s time to start the Angular 8 app to check out the File upload demo in the browser. Run the following command:

ng serve --open

Make sure your NODE server must be running to manage the backend.

When you upload the image from front-end you’ll see your image files are saving inside the backend > uploads folder.

Conclusion

In this Angular 8 tutorial, we barely scratched the surface related to file uploading in a Node application. There are various other methods available on the internet through which you can achieve file uploading task quickly. However, this tutorial is suitable for beginners developers. I hope this tutorial will surely help and you if you liked this tutorial, please consider sharing it with others.

How to Implement Server Side Pagination with Angular 8 and Node.js

How to Implement Server Side Pagination with Angular 8 and Node.js

A simple example of how to implement server-side pagination in Angular 8 with a Node.js backend API.

This is a simple example of how to implement server-side pagination in Angular 8 with a Node.js backend API.

The example contains a hard coded array of 150 objects split into 30 pages (5 items per page) to demonstrate how the pagination logic works. Styling of the example is done with Bootstap 4.

The tutorial code is available on GitHub at https://github.com/cornflourblue/angular-8-node-server-side-pagination.

Here it is in action (may take a few seconds for the container to startup):

Running the Angular + Node Pagination Example Locally
  1. Install NodeJS and NPM from https://nodejs.org/en/download/.
  2. Download or clone the tutorial project source code from https://github.com/cornflourblue/angular-8-node-server-side-pagination.
  3. Install required npm packages of the backend Node API by running the npm install command in the /server folder.
  4. Start the backend Node API by running npm start in the /server folder, this will start the API on the URL http://localhost:4000.
  5. Install required npm packages of the frontend Angular app by running the npm install command in the /client folder.
  6. Start the Angular frontend app by running npm start in the /client folder, this will build the app with webpack and automatically launch it in a browser on the URL http://localhost:8080.
Server-Side (Node.js) Pagination Logic

Below is the code for the paged items route (/api/items) in the node server file (/server/server.js) in the example, it creates a hardcoded list of 150 items to be paged, in a real application you would replace this with real data (e.g. from a database). The route accepts an optional page parameter in the url query string, if the parameter isn't set it defaults to the first page.

The paginate() function is from the jw-paginate package and accepts the following parameters:

  • totalItems (required) - the total number of items to be paged
  • currentPage (optional) - the current active page, defaults to the first page
  • pageSize (optional) - the number of items per page, defaults to 10
  • maxPages (optional) - the maximum number of page navigation links to display, defaults to 10

The output of the paginate function is a pager object containing all the information needed to get the current pageOfItems out of the items array, and to display the pagination controls in the Angular frontend, including:

  • startIndex - the index of the first item of the current page (e.g. 0)
  • endIndex - the index of the last item of the current page (e.g. 9)
  • pages - the array of page numbers to display (e.g. [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ])
  • currentPage - the current active page (e.g. 1)
  • totalPages - the total number of pages (e.g. 30)

I've set the pageSize to 5 in the CodeSandbox example above so the pagination links aren't hidden below the terminal console when the container starts up. In the code on GitHub I didn't set the page size so the default 10 items are displayed per page in that version.

The current pageOfItems is extracted from the items array using the startIndex and endIndex from the pager object. The route then returns the pager object and current page of items in a JSON response.

// paged items route
app.get('/api/items', (req, res, next) => {
    // example array of 150 items to be paged
    const items = [...Array(150).keys()].map(i => ({ id: (i + 1), name: 'Item ' + (i + 1) }));

    // get page from query params or default to first page
    const page = parseInt(req.query.page) || 1;

    // get pager object for specified page
    const pageSize = 5;
    const pager = paginate(items.length, page, pageSize);

    // get page of items from items array
    const pageOfItems = items.slice(pager.startIndex, pager.endIndex + 1);

    // return pager object and current page of items
    return res.json({ pager, pageOfItems });
});

Client-Side (Angular 8) Pagination Components

Since the pagination logic is handled on the server, the only thing the Angular client needs to do is fetch the pager information and current page of items from the backend, and display them to the user.

Angular Home Component

Below is the Angular home component (/client/src/app/home/home.component.ts) from the example. The loadPage() method fetches the pager object and pageOfItems for the specified page from the backend API with an HTTP request, and the ngOnInit() gets the page parameter in the route query params or defaults to the first page if the query param is empty.

By subscribing to the route.queryParams observable, the home component is notified whenever the page changes so it can load the new page.

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';

@Component({ templateUrl: 'home.component.html' })
export class HomeComponent implements OnInit {
    pager = {};
    pageOfItems = [];

    constructor(
        private http: HttpClient,
        private route: ActivatedRoute
    ) { }

    ngOnInit() {
        // load page based on 'page' query param or default to 1
        this.route.queryParams.subscribe(x => this.loadPage(x.page || 1));
    }

    private loadPage(page) {
        // get page of items from api
        this.http.get(`/api/items?page=${page}`).subscribe(x => {
            this.pager = x.pager;
            this.pageOfItems = x.pageOfItems;
        });
    }
}

Angular Home Component Template

The home component template (/client/src/app/home/home.component.html) renders the current page of items as a list of divs with the *ngFor directive, and renders the pagination controls using the data from the pager object. Each pagination link sets the page query parameter in the url with the routerLink directive and [queryParams] property.

The CSS classes used are all part of Bootstrap 4.3, for more info see https://getbootstrap.com/docs/4.3/getting-started/introduction/.


    ### Angular 8 + Node - Server Side Pagination Example

    
        {{item.name}}

    
    
        
            
                First
            
            
                Previous
            
            
                {{page}}
            
            
                Next
            
            
                Last
            
        
    


I hope this tutorial will surely help and you if you liked this tutorial, please consider sharing it with others.

Thanks For Visiting, Keep Visiting.

Difference between Node.js and React.js and Angular.js

AngularJS turned the whole definition of web development through its existence, what was lacking in previous methods of development was inappropriate structure. It merges use of JavaScript with MVC and HTML with DOM. ReactJS is a JavaScript library and has a view, but lacks model &amp; controller components. React introduced the concept of virtual DOM which is regarded as one of the greatest advantages of React in comparison of AngularJS. Node.js is a JavaScript-based platform used for developing real-time network applications like a chat software.

AngularJS turned the whole definition of web development through its existence, what was lacking in previous methods of development was inappropriate structure. It merges use of JavaScript with MVC and HTML with DOM. ReactJS is a JavaScript library and has a view, but lacks model & controller components. React introduced the concept of virtual DOM which is regarded as one of the greatest advantages of React in comparison of AngularJS. Node.js is a JavaScript-based platform used for developing real-time network applications like a chat software.