Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Build A Time-Based One-Time Password Manager With NativeScript

Not too long ago I released a time-based one-time password manager called OTP Safe to Google Play and iTunes. That particular application was built with Ionic Framework and I even wrote a tutorial explaining how to make a similar 2FA manager with Ionic 2. Being a hybrid mobile application, there were some performance limitations that came with the Ionic 2 application. This inspired me to convert the application to something native and NativeScript seemed like a solid solution.

Let’s take time-based one-time password management to the next level and create a native mobile application with NativeScript and Angular.

In case you’re unfamiliar, two-factor authentication (2FA) is an extra layer of security for web applications, requiring you to enter a numeric password that changes every 30 seconds in addition to your standard password. A popular application that accomplishes this management is Google Authenticator. However, Google Authenticator leaves a lot to be desired and while we won’t cover anything too crazy, it opens the door to your imagination.

Before getting too far ahead of ourselves, it is probably a good idea to see what we’re going to be building.

NativeScript One-Time Password Example

Above is an animated screenshot of what the application will do. It has two screens, one for listing time-based one-time passwords that change every 30 seconds and one screen for adding new passwords with a secret key. At the bottom of the list screen there is a counter that will reset every 30 seconds.

Much of the code for generating passwords from secret keys was inspired by the following two sources:

I definitely want to give those two sources credit for sharing the algorithm on generating these passwords. I did a generic JavaScript implementation of time-based one-time passwords in a previous post on this blog.

The Requirements

There are a few prerequisites that must be met in order to make this tutorial a success.

  • Node.js 4+
  • NativeScript 2.4+
  • The Android SDK and / or Xcode

NativeScript, like many cross platform frameworks, requires Node.js to operate. This is because the CLI along with all dependencies are obtained via the Node Package Manager (NPM). While you can probably use NativeScript 2.0 or higher, the particular version I’m using is 2.4. It can be installed as per the instructions on the NativeScript website. To build for the Android platform, the Android SDK must be installed and configured. To build for the iOS platform, Xcode must be installed and configured. You don’t need both, but it doesn’t hurt.

Creating a New NativeScript Project with Dependencies

To keep this example easy to understand we’re going to work with a fresh project. Assuming NativeScript is already installed and configured, execute the following from your Command Prompt (Windows) or Terminal (Mac and Linux):

tns create ns-otp-manager --ng
cd ns-otp-manager
tns platform add ios
tns platform add android

The above commands will create a new Angular project for NativeScript and add the necessary build platforms. Keep in mind that you’ll need Xcode for iOS and the Android SDK for Android.

This project has a dependency for persisting data. While you could use things like SQLite or similar, we’re going to opt for an easier NoSQL solution. In this example we’ll be using the open source Couchbase NativeScript plugin.

To install the Couchbase plugin, execute the following:

tns plugin add nativescript-couchbase

Because we’re using Angular which uses TypeScript, it would be a good idea to make use of the included type definitions. To add these type definitions, open the project’s references.d.ts file and include the following:

/// <reference path="./node_modules/nativescript-couchbase/couchbase.d.ts" />

We have another dependency to include in this project. To save us from having to create our own hashing algorithms, we’re going to use one of my favorite JavaScript libraries, jsSHA. This library will allow us to take password secret keys and convert them into numeric one-time passwords based on the timestamp.

To include jsSHA in your project, execute the following from the command line:

npm install [email protected] --save

TypeScript type definitions for jsSHA are a little broken, so we won’t be included them in our NativeScript project. At this point we can start the development process.

Creating the Password Generator Provider

We’re going to skim over this step rather fast as most of it surrounds the necessary algorithms for hashing the secret keys. It doesn’t necessarily pertain to our goals in the application.

Create an app/providers/generator/generator.ts file and any directories found in that path. In this file include the following code:

import { Injectable } from '@angular/core';
import * as jsSHA from "jssha";

@Injectable()
export class Generator {

    constructor() {}

    private dec2hex(value: number) {
        return (value < 15.5 ? "0" : "") + Math.round(value).toString(16);
    }

    private hex2dec(value: string) {
        return parseInt(value, 16);
    }

    private leftpad(value: string, length: number, pad: string) {
        if(length + 1 >= value.length) {
            value = Array(length + 1 - value.length).join(pad) + value;
        }
        return value;
    }

    private base32tohex(base32: string) {
        let base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
        let bits = "";
        let hex = "";
        for(let i = 0; i < base32.length; i++) {
            let val = base32chars.indexOf(base32.charAt(i).toUpperCase());
            bits += this.leftpad(val.toString(2), 5, '0');
        }
        for(let i = 0; i + 4 <= bits.length; i+=4) {
            let chunk = bits.substr(i, 4);
            hex = hex + parseInt(chunk, 2).toString(16) ;
        }
        return hex;
    }

    public getOTP(secret: string) {
        try {
            let epoch = Math.round(new Date().getTime() / 1000.0);
            let time = this.leftpad(this.dec2hex(Math.floor(epoch / 30)), 16, "0");
            let hmacObj = new jsSHA(time, "HEX");
            let hmac = hmacObj.getHMAC(this.base32tohex(secret), "HEX", "SHA-1", "HEX");
            let offset = this.hex2dec(hmac.substring(hmac.length - 1));
            var otp = (this.hex2dec(hmac.substr(offset * 2, 8)) & this.hex2dec("7fffffff")) + "";
            otp = (otp).substr(otp.length - 6, 6);
        } catch (error) {
            throw error;
        }
        return otp;
    }

}

To give credit where credit is deserved, most of the code in the above provider was taken from Tin Isles and the Google Authenticator repository. We’re adding this code to a provider so we can conveniently access the functions without being wasteful.

Everything that comes next exercises the beauty of NativeScript and the entire creation process of the application.

Creating the Database Provider

As mentioned previously, we’re using Couchbase NoSQL for storing all of our password data. It is convenient to use NoSQL in a NativeScript application because NoSQL databases typically store JSON data, a native data format in JavaScript and TypeScript applications. It is a flexible and very easy to use storage method.

Create an app/providers/database/database.ts file and any directory in the path. Within this provider file, include the following code:

import { Injectable } from "@angular/core";
import { Couchbase } from "nativescript-couchbase";

@Injectable()
export class Database {

    private db: any;

    public constructor() {
        if(!this.db) {
            this.db = new Couchbase("passwords");
            this.db.createView("passwords", "1", (document, emitter) => {
                emitter.emit(document._id, document);
            });
        }
    }

    public getDatabase() {
        return this.db;
    }

}

So what is happening in the above code?

We’re importing the NativeScript plugin that we previously downloaded and we’re creating a class called Database. This class has a private variable and is a singleton class meaning the same instance will be used throughout the application.

If the database was not already instantiated, a passwords database will be created and opened. This is followed by the creation of a Couchbase MapReduce View.

So what is a MapReduce View?

A Couchbase View will index the data in your local database based on supplied logic. This view can be queried very quickly based on the logic that was put in place. So in our example:

this.db.createView("passwords", "1", (document, emitter) => {
    emitter.emit(document._id, document);
});

When we query the passwords view a key-value pair for all documents will be returned. There is no surrounding logic for the data being returned. The key-value pair is the document id and the full document.

Because we’re planning to use the same database instance throughout the open application, we need to return the open instance.

Bootstrapping the Application Providers

Any provider you create must be added to Angular’s @NgModule block. This can be found or added to the app/app.module.ts file.

Within your project’s app/app.module.ts file, include the following provider imports:

import { Database } from "./providers/database/database";
import { Generator } from "./providers/generator/generator";

With the providers imported they can be added to the @NgModule. It might look like the following:

@NgModule({
    declarations: [AppComponent],
    bootstrap: [AppComponent],
    imports: [
        NativeScriptFormsModule,
        NativeScriptModule,
    ],
    providers: [Database, Generator]
})

Notice the following line:

providers: [Database, Generator]

The above is where we add the providers in particular. At this point they can be injected throughout the application.

Creating a Component for Listing Saved Passwords

As seen in the animation earlier, we’re going to be building a two page application, the default page being a list of saved passwords. To accomplish such a page, we need to create an Angular component.

In your project, create an app/components/list directory with a list.ts and list.html file inside. The TypeScript file will hold all the logic for this particular page and the HTML file will hold all of the UI.

Starting with the app/components/list/list.ts file, open it and include the following. We’ll break it down after.

import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { Location } from "@angular/common";
import { Database } from "../../providers/database/database";
import { Generator } from "../../providers/generator/generator";

@Component({
    selector: "list",
    templateUrl: "./components/list/list.html",
})
export class ListComponent implements OnInit {

    public passwords: Array<any>;
    public countdown: number;
    public epochSnapshot: number;

    public constructor(private router: Router, private location: Location, private couchbase: Database, private generator: Generator) {
        this.passwords = [];
        this.epochSnapshot = 0;
    }

    public ngOnInit() {
        this.location.subscribe(path => {
            this.retrievePasswords();
        });
        this.retrievePasswords();
        this.ticker();
    }

    private ticker() {
        let epoch = Math.round(new Date().getTime() / 1000.0);
        this.countdown = (30 - (epoch % 30));
        if(epoch % 30 == 0) {
            if(epoch > this.epochSnapshot + 5) {
                this.epochSnapshot = epoch;
                this.retrievePasswords();
            }
        }
        setTimeout(() => {
            this.ticker();
        }, 100);
    }

    public create() {
        this.router.navigate(["create"]);
    }

    private retrievePasswords() {
        this.passwords = [];
        let rows = this.couchbase.getDatabase().executeQuery("passwords");
        for(let i = 0; i < rows.length; i++) {
            this.passwords.push({
                "title": rows[i].title,
                "password": this.generator.getOTP(rows[i].secret)
            });
        }
    }

}

The first thing you’ll notice in the above is several import statements, importing not only Angular components, but the custom providers that we had previously created. From the set of Angular components, we’re going to be using the Router for navigation, Location for tracking movements in the navigation stack, and OnInit for loading our data.

Inside the ListComponent class we have three public variables. The passwords array will hold all the password objects, where every object has a title and a secret key. It is public because we want to render it to the screen. The countdown numeric variable will hold our 30 second timer at the footer of the application and the epochSnapshot will help us track our elapsed time for tracking when 30 seconds have occurred.

The constructor method allows us to accomplish two different things. First, it lets us initialize our variables like a constructor should, but in Angular, it also lets us inject various providers to be used within this particular component. For example we are injecting our custom providers as well as the others that we had in our import statements.

It is bad practice to load data in the constructor method, so we use the implemented ngOnInit method instead:

public ngOnInit() {
    this.location.subscribe(path => {
        this.retrievePasswords();
    });
    this.retrievePasswords();
    this.ticker();
}

The above method will call several methods that we’ve not yet implemented. However, it does this after the constructor method has finished. Within the ngOnInit method we subscribe to the location status so when a back navigation happens we can trigger a reload of our passwords. We do this because neither the constructor or ngOnInit methods get triggered during a back navigation event. The retrievePasswords and ticker methods will either load the passwords from the database or countdown the timer.

Starting with the password retrieval:

private retrievePasswords() {
    this.passwords = [];
    let rows = this.couchbase.getDatabase().executeQuery("passwords");
    for(let i = 0; i < rows.length; i++) {
        this.passwords.push({
            "title": rows[i].title,
            "password": this.generator.getOTP(rows[i].secret)
        });
    }
}

The above code will reset our public list of passwords and query the Couchbase View we had created earlier. The result set will be looped through, converting the secret key into a numeric one-time password via the provider that we created. These results are added to the public array for display on the screen.

The ticker method will reload the passwords every 30 seconds as seen in the following:

private ticker() {
    let epoch = Math.round(new Date().getTime() / 1000.0);
    this.countdown = (30 - (epoch % 30));
    if(epoch % 30 == 0) {
        if(epoch > this.epochSnapshot + 5) {
            this.epochSnapshot = epoch;
            this.retrievePasswords();
        }
    }
    setTimeout(() => {
        this.ticker();
    }, 100);
}

To prevent the application from getting confused and blowing up, we don’t immediately perform recursion. Instead we set a timeout and only call the method again once 100 milliseconds have passed.

The last method we have is the one used for navigation:

public create() {
    this.router.navigate(["create"]);
}

Although we haven’t defined the navigation path yet, we’ll be able to navigate to the second page via the create path.

Everything above was related to the application logic. We still need to design a UI to be used with all the TypeScript we just implemented.

Open the project’s app/components/list/list.html file and include the following markup:

<ActionBar title="NS OTP Manager">
    <ActionItem text="Add" ios.position="right" (tap)="create()"></ActionItem>
</ActionBar>
<GridLayout rows="* auto" columns="*">
    <ListView [items]="passwords" row="0" col="0">
        <template let-password="item">
            <GridLayout class="password-row" rows="auto" columns="* auto">
                <Label [text]="password.title" class="password-title" row="0" col="0"></Label>
                <Label [text]="password.password" row="0" col="1"></Label>
            </GridLayout>
        </template>
    </ListView>
    <Label text="{{ countdown }} seconds remaining..." class="timer" row="1" col="0"></Label>
</GridLayout>

In the above markup we have an action bar, sometimes referred to as a navigation bar. It has a single button in it, that when pressed, will start the navigation to the next screen via the public method we had created in the TypeScript file.

The core content of this page is a ListView where each row in the list is a two column grid. The items populated in the ListView are the public passwords defined in the [items]="passwords" attribute. Each item in that array will be identified as password.

Because the ListView is inside of a GridLayout, we were able to define how much space the ListView takes up in comparison to the rest of the screen. After all we need space for the footer at the bottom.

More information on sizing a GridLayout can be seen in a previous article I wrote.

Creating a Component for Adding New Passwords

The first, and probably most complicated component of this particular application has been completed, but we still have one more to go. We need a way to save data into the database that we’re loading from.

Create an app/components/create directory in your project with a create.ts and create.html file in it. Just like with the previous component we’re going to start with the TypeScript logic.

Open the project’s app/components/create/create.ts file and include the following TypeScript code:

import { Component } from "@angular/core";
import { Location } from "@angular/common";
import { Database } from "../../providers/database/database";

@Component({
    selector: "create",
    templateUrl: "./components/create/create.html",
})
export class CreateComponent {

    public password: any;

    public constructor(private location: Location, private couchbase: Database) {
        this.password = {
            "title": "",
            "secret": ""    
        };
    }

    public save() {
        if(this.password.title && this.password.secret) {
            this.couchbase.getDatabase().createDocument(this.password);
            this.location.back();
        }
    }

}

Let’s break down what is happening in the above code.

First we need to import various Angular components into our application along with the custom database provider. We’re going to use Location for navigating backwards in the stack and the Database for saving data. You’ll notice there are fewer imports than the previous page. It is because we don’t need them here.

Inside the CreateComponent class we have a public variable that will hold our form data. In other words it will contain any user input data. The constructor method will allow us to initialize this variable as well as inject any necessary providers that we plan to use within the component.

The save method is where all the magic happens:

public save() {
    if(this.password.title && this.password.secret) {
        this.couchbase.getDatabase().createDocument(this.password);
        this.location.back();
    }
}

We can take the object that contains the user input data and save it directly into the database. Don’t you love NoSQL? Once saved, we can navigate backwards in the stack to the list page.

The TypeScript was much shorter, but we still have to create the UI for it.

Open the project’s app/components/create/create.html file and include the following markup:

<ActionBar title="NS OTP Manager">
    <NavigationButton text="Back" ios.position="left"></NavigationButton>
    <ActionItem text="Save" ios.position="right" (tap)="save()"></ActionItem>
</ActionBar>
<StackLayout>
    <Label text="Title" class="input-label"></Label>
    <TextField [(ngModel)]="password.title"></TextField>
    <Label text="Secret" class="input-label"></Label>
    <TextField [(ngModel)]="password.secret"></TextField>
</StackLayout>

In this UI we have an action bar, but this time we have a button for navigating backwards and a button for performing our save function. We don’t need to add the back button if we don’t want to. We are doing this so we can choose the text displayed.

Inside the StackLayout we have two Label entries and two TextField entries. The text fields are bound to our public TypeScript variable through the use of the [(ngModel)] attribute. This creates a two way binding.

Using CSS to Style the UI Components

Out of the box, the UI components look very plain. NativeScript doesn’t like to force stylings on you. Instead you get the base UI that iOS and Android provides. However, this can be adjusted through CSS.

You may have noticed that we were adding class attributes to both pages of UI. These classes are defined in the project’s app/app.css file. Open this file and include the following:

.password-row {
    padding: 15;
}

.password-title {
    font-weight: bold;
}

.input-label {
    font-weight: bold;
    padding-top: 5;
    padding-bottom: 5;
}

.timer {
    padding: 15;
    background-color: #F0F0F0;
}

All global style data goes into this file and it is the same CSS that you’d find in HTML. Just keep in mind that this is a CSS subset, so while it uses HTML CSS, not all CSS is available in NativeScript.

Adding Application Routes for Navigation

With both components created, they don’t exactly do anything as of now. They are orphaned components until we set up the routing information. To change this we need to create an app/app.routing.ts file that includes the following code:

import { Routes } from "@angular/router";
import { ListComponent } from "./components/list/list";
import { CreateComponent } from "./components/create/create";

export const appRoutes: Routes = [
    { path: '', component: ListComponent },
    { path: "create", component: CreateComponent }
];

export const appComponents: any = [
    ListComponent,
    CreateComponent
];

Every navigation component must be imported into this file. The route with the empty path is the default route and will shown as soon as the application loads. Every other route can be navigated to via its path.

We need to export each of the components to be added to the previously seen @NgModule. Open the project’s app/app.module.ts file and include the following import:

import { appRoutes, appComponents } from "./app.routing";
import { NativeScriptRouterModule } from "nativescript-angular/router";

Notice we’re also importing the NativeScriptRouterModule module.

With the appropriate things imported, they can be added to our @NgModule block. Check out this app/app.module.ts file:

import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { NativeScriptFormsModule } from "nativescript-angular/forms";

import { AppComponent } from "./app.component";
import { Database } from "./providers/database/database";
import { Generator } from "./providers/generator/generator";
import { appRoutes, appComponents } from "./app.routing";

@NgModule({
    declarations: [AppComponent, ...appComponents],
    bootstrap: [AppComponent],
    imports: [
        NativeScriptModule,
        NativeScriptFormsModule,
        NativeScriptRouterModule,
        NativeScriptRouterModule.forRoot(appRoutes)
    ],
    providers: [Database, Generator],
    schemas: [NO_ERRORS_SCHEMA]
})
export class AppModule { }

Above is what our completed app/app.module.ts file looks like.

We’re not done yet though. Our routes are connected, but we have no pass-through point. Open the project’s app/app.component.ts file and replace all the code with the following:

import {Component} from "@angular/core";

@Component({
    selector: "my-app",
    template: "<page-router-outlet></page-router-outlet>",
})
export class AppComponent {

}

Notice in particular the use of the <page-router-outlet> tags. This is where each of the navigation passes through. In theory if you wanted to you could add some master template around these tags.

Testing the Application on iOS and Android

At this point the application should be very usable. To test this application, there are numerous ways to do this, but we’re just going to emulate it.

From the Command Prompt or Terminal, execute the following:

tns emulate [platform]

Remember to replace [platform] with either android or ios depending on the platform you wish to build for.

If you’d like test out the completed application, you can download it here. However, you cannot just emulate this application because the plugins and other dependencies won’t have been downloaded in order to save space.

From the downloaded and extracted project, execute the following to get all dependencies:

tns install

Now you can proceed to emulating the application.

Conclusion

You just saw how to build your own time-based one-time password manager using NativeScript and Angular. While I did this same tutorial for Ionic 2, the difference here is that you’re getting a high performance native application now.

In a production application you probably shouldn’t store these password secrets in plain text. You can check out a previous tutorial I wrote for encrypting data in a NativeScript application. With some imagination you can encrypt the password secrets when they go in and decrypt them when you need to display them on the screen in hashed format.

A lot of time went into the attached project archive. Please do not share it without my consent.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in Java, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Apache Cordova. Nic writes about his development experiences related to making web and mobile development easier to understand.

Search

Follow Us

Support This Site