A few years back I created an Android and iOS application called OTP Safe that managed time-based one-time passwords. This application was made with the first version of Ionic Framework and at the time was great because it accomplished more than the Google Authenticator application. Now that Ionic 2 is approaching stable release, it seemed like a cool idea to take this one-time password application and build it with the latest and greatest including Angular.
We’re going to see how to create an iOS and Android time-based one-time password manager using Ionic 2, Angular, and TypeScript.
Before going forward with the code, it is probably a good idea to get an idea about what we’re going to build and how we’re going to build it.
Based on the above animated screenshot, you can see that this application will have two screens. The first screen will contain a list of already added passwords. These passwords are six digit codes generated based on the timestamp and secret key provided. Every thirty seconds, based on the timer in the footer, the passwords will change. Clicking on any password will result in the password being copied to the clipboard.
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.
There are on a few requirements to making this application a success.
Node.js is required because it comes with the Node Package Manager (NPM). This is used for installing not only the Ionic 2 CLI, but it also plays a part in installing the necessary plugins that this application needs. The Android SDK is needed if you wish to build for Android and Xcode is required if you wish to build for iOS. Note that Xcode is only available for Mac.
To make this project easy to understand, we’re going to start from scratch. From the Command Prompt (Windows) or Terminal (Linux and Mac), execute the following:
ionic start OtpProject blank --v2
cd OtpProject
ionic platform add ios
ionic platform add android
The --v2
tag indicates an Angular project with TypeScript. In other words, an Ionic 2 project. While I’ve added the iOS platform, if you aren’t using a Mac with Xcode installed, you cannot build for iOS.
This project will make use of SQLite, the platform clipboard, and Toast notifications, all of which require Apache Cordova plugins to be installed.
To install the required plugins, execute the following from the Terminal or Command Prompt:
ionic plugin add cordova-sqlite-storage
ionic plugin add cordova-plugin-x-toast
ionic plugin add https://github.com/VersoSolutions/CordovaClipboard.git
Getting ahead of ourselves here, we’re going to be using Ionic Native in our project which makes use of these Apache Cordova plugins.
To make the password generation possible we’ll need to use a library that includes HMAC. A great library for this is jsSHA as I’ve used in numerous tutorials before.
To include this library, execute the following command:
npm install jssha@1.6.2 --save
While we just included the JavaScript library, we’ve not included the type definitions for TypeScript. For this project we won’t worry about the type definitions, but more information on using jsSHA with type definitions, can be found in a previous article that I wrote.
Every password that we create will be stored in a SQLite database. However, it is best practice to use a shared service rather than working directly with the data layer.
From the Command Prompt or Terminal, execute the following:
ionic g provider database
The above command uses the Ionic 2 generators to create a new provider. This provider is found in the src/providers/database.ts file. Open this file and include the following code:
import { Injectable } from '@angular/core';
import { SQLite } from "ionic-native";
@Injectable()
export class Database {
private isInstantiated: boolean;
private storage: SQLite;
public constructor() {
if(!this.isInstantiated) {
this.storage = new SQLite();
this.storage.openDatabase({name: "data.db", location: "default"}).then(() => {
this.storage.executeSql("CREATE TABLE IF NOT EXISTS passwords (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, secret TEXT)", []);
this.isInstantiated = true;
});
}
}
public create(title: string, secret: string) {
return new Promise((resolve, reject) => {
this.storage.executeSql("INSERT INTO passwords (title, secret) VALUES (?, ?)", [title, secret]).then((data) => {
resolve({
"title": title,
"secret": secret
});
}, (error) => {
reject(error);
});
});
}
public retrieve(): Promise<any> {
return new Promise((resolve, reject) => {
this.storage.executeSql("SELECT id, title, secret FROM passwords", []).then((data) => {
if(data.rows.length > 0) {
let passwords = [];
for(let i = 0; i < data.rows.length; i++) {
passwords.push({
"id": data.rows.item(i).id,
"title": data.rows.item(i).title,
"secret": data.rows.item(i).secret,
});
}
resolve(passwords);
}
}, (error) => {
reject(error);
});
});
}
}
There is a lot happening in the above code so we should probably break it down to get a better understanding.
We’re going to use the SQLite Ionic Native component which wraps the Apache Cordova SQLite library.
Inside the Database
class we have a private variable called isInstantiated
which represents if the database instance is already created and open. We don’t want to keep opening a new instance in this application. The other private variable, storage
, will hold our open database.
The constructor
method is where we check to see if we had instantiated the class and if not, create a new database table for holding our data.
This leaves us with two public methods. The create
method will take a password title and a secret key and insert them into the database using SQL.
public create(title: string, secret: string) {
return new Promise((resolve, reject) => {
this.storage.executeSql("INSERT INTO passwords (title, secret) VALUES (?, ?)", [title, secret]).then((data) => {
resolve({
"title": title,
"secret": secret
});
}, (error) => {
reject(error);
});
});
}
The database interactions are asynchronous which is why we return a promise when inserting data. The result will be an object of what we inserted.
The other public method, retrieve
, will allow us to get all our saved data.
public retrieve(): Promise<any> {
return new Promise((resolve, reject) => {
this.storage.executeSql("SELECT id, title, secret FROM passwords", []).then((data) => {
if(data.rows.length > 0) {
let passwords = [];
for(let i = 0; i < data.rows.length; i++) {
passwords.push({
"id": data.rows.item(i).id,
"title": data.rows.item(i).title,
"secret": data.rows.item(i).secret,
});
}
resolve(passwords);
}
}, (error) => {
reject(error);
});
});
}
This is where things get interesting. We don’t want to be bothered with SQL everywhere in our application. Instead we take the results of the query, and load it into an array of objects, something much more compatible with our JavaScript application.
Just like with the other method, we are returning a promise. More information on shared services can be seen in a previous post I did on the subject.
Remember that time-based one-time password algorithm I was talking about? We’re going to create a shared service out of it to be used throughout the application. It will definitely make our life easier.
Using the Command Prompt or Terminal, execute the following:
ionic g provider PasswordGenerator
The above command will use the Ionic 2 generators to create a service for our password generator code. The command will create a file at src/providers/password-generator.ts that will eventually contain a few methods. Open this file and add the following code:
import { Injectable } from '@angular/core';
import * as jsSHA from "jssha";
@Injectable()
export class PasswordGenerator {
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;
}
}
While there is quite a bit happening above, most of it is related to transforming and padding hexadecimal and decimal values. This is all related to the algorithms around generating passwords.
More information can be seen on Tin Isles by Russ Sayers.
Now we can start developing the actual pages of the application. The page that we create now will handle listing of the passwords and display a countdown timer regarding when the passwords will change.
To create this page, execute the following:
ionic g page list
The Ionic generator used above will create an HTML, TypeScript and SCSS file for our page. These files are found in the src/pages/list directory.
Before we start coding the new page, we need to set it as the default. In a fresh Ionic 2 template, the default page is HomePage
found at src/pages/home/home.ts, but we won’t be using this page. To make the switch, open src/app/app.component.ts and include the following code:
import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar, Splashscreen } from 'ionic-native';
import { ListPage } from '../pages/list/list';
@Component({
templateUrl: 'app.html'
})
export class MyApp {
rootPage = ListPage;
constructor(platform: Platform) {
platform.ready().then(() => {
StatusBar.styleDefault();
Splashscreen.hide();
});
}
}
In the above code we’ve swapped out the occurrences of HomePage
with ListPage
.
With the page defaulting out of the way, we can now focus on the page code. Open the project’s src/pages/list/list.ts file and include the following code:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Clipboard, Toast } from "ionic-native";
import { CreatePage } from "../create/create";
import { PasswordGenerator } from "../../providers/password-generator";
import { Database } from "../../providers/database";
@Component({
templateUrl: 'list.html',
})
export class ListPage {
public passwords: Array<any>;
public countdown: number;
private epochSnapshot: number;
public constructor(private navCtrl: NavController, private passwordGenerator: PasswordGenerator, private database: Database) {
this.passwords = [];
this.epochSnapshot = 0;
}
public ionViewDidEnter() {
this.retrievePasswords();
this.ticker();
}
public create() {
this.navCtrl.push(CreatePage);
}
public copy(value: string) {
Toast.show("Copied to clipboard!", "3000", "bottom").subscribe(
toast => {
Clipboard.copy(value);
}
);
}
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);
}
private retrievePasswords() {
this.database.retrieve().then(results => {
this.passwords = [];
for(let i = 0; i < results.length; i++) {
this.passwords.push({
"title": results[i].title,
"password": this.passwordGenerator.getOTP(results[i].secret)
});
}
}, error => {
console.error(error);
});
}
}
Let’s break down everything that is happening in the above code.
First of all, we have all of our imports that import various Angular and Ionic 2 components. We’re also importing the services that we created. What really matters here is the content that is found in the ListPage
class.
We have two public variables, passwords
and countdown
. These are variables that will be presented in the UI. The private echoSnapshot
helps us manage our countdown timer, but it will never be accessed from the UI.
In the constructor
method, various services are injected for future use and our variables are initialized. It is bad practice to load data in the constructor
method, which is why we have the ionViewDidEnter
method which does this. The ionViewDidEnter
method queries our database and then starts our timer.
Skipping around, let’s take a look at the method for loading our data:
private retrievePasswords() {
this.database.retrieve().then(results => {
this.passwords = [];
for(let i = 0; i < results.length; i++) {
this.passwords.push({
"title": results[i].title,
"password": this.passwordGenerator.getOTP(results[i].secret)
});
}
}, error => {
console.error(error);
});
}
In the above code we query the database through our provider and add each of the result items to our array of passwords. However, we don’t just add them directly to the array. We first need to convert the secret key using the password generator provider. This will give us a six digit code instead.
When it comes to our countdown timer, we have the following code:
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);
}
We are first getting the system time. Time-based one-time passwords are designed to change every 30 seconds which is why we are doing epoch % 30
for our public variable. If the system time equals zero it means we need to change our six digit passwords again. However, doing that will result in the conversions happening many times because of how fast the device or simulator can keep up. This is why we want to also make sure our time is greater than the snapshot time.
If all these conditions are met, we reset the snapshot and retrieve the passwords again with the new values. Then we rinse, and repeat (start over).
We’re not quite done though. We also have a create
and copy
method. When we wish to create a password, we’re just routed to the next page. When we want to copy a password, we first show a Toast notification of the event, and then store the password value in the clipboard.
With the TypeScript logic out of the way we can now focus on the HTML UI.
Open the project’s src/pages/list/list.html file and include the following markup:
<ion-header>
<ion-navbar>
<ion-title>Time-Based Passwords</ion-title>
<ion-buttons end>
<button (click)="create()">
<ion-icon name="add"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-list>
<ion-item *ngFor="let password of passwords" (click)="copy(password.password)">
{{password.password}}
<ion-note item-right>{{password.title}}</ion-note>
</ion-item>
</ion-list>
</ion-content>
<ion-footer padding-left>
<p>{{countdown}} seconds remaining...</p>
</ion-footer>
As you can see there is a lot less going on with the UI then there was with the TypeScript logic.
We start our UI with a navigation bar that contains a title and one button. When the button is clicked, the create
method from our TypeScript file is executed which navigates us to the next page.
The core content of this page is the list. We loop through each of the items in the public passwords
array, presenting the title and six digit password on the screen. When any item is clicked, the password for that particular item is passed into the copy
method.
At the bottom of the screen we have a footer that shows the public countdown
variable. When it hits zero, it resets to 30 and the passwords change.
With the first page out of the way, now we can focus on adding new passwords to the application. Like with every other page and provider, we need to generate a new page to accommodate us. From the Command Prompt or Terminal, execute the following:
ionic g page create
The above command will generate an HTML, TypeScript, and SCSS file found in the src/pages/create directory.
We’re going to start by editing the TypeScript logic before we touch the HTML UI. Open the project’s src/pages/create/create.ts file and include the following code:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Database } from "../../providers/database";
@Component({
templateUrl: 'create.html',
})
export class CreatePage {
public title: string;
public secret: string;
public constructor(private navCtrl: NavController, private database: Database) {
this.title = "";
this.secret = "";
}
public save() {
if(this.title && this.secret) {
this.database.create(this.title, this.secret).then(result => {
this.navCtrl.pop();
}, error => {
console.error(error);
});
}
}
}
Not as wild as the first page, right?
Inside the CreatePage
class we have two public variables that will be bound to a form in the UI. One will represent data input into a title
field and one will represent data input into a secret
field. In the constructor
method we initialize these variables as well as inject any relevant services.
When the user decides to save, we first check that title
and secret
are not null or empty. If this is true, pass the variables into our database service so they can be saved to the database. Upon a successful save, pop backwards in the navigation stack to the previous page.
This brings us to the UI that pairs with the TypeScript logic.
Open the project’s src/pages/create/create.html file and include the following markup:
<ion-header>
<ion-navbar>
<ion-title>Time-Based Passwords</ion-title>
<ion-buttons end>
<button (click)="save()">
<ion-icon name="checkmark"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-list>
<ion-item>
<ion-label floating>Title</ion-label>
<ion-input type="text" [(ngModel)]="title"></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Secret</ion-label>
<ion-input type="text" [(ngModel)]="secret"></ion-input>
</ion-item>
</ion-list>
</ion-content>
The above HTML is very similar to what we saw in the previous page.
We have a navigation bar that contains a page title and a button for saving data. In the core content of the page we have a list, but this time we’re not looping through data. This time we have two form fields for collecting user input. These form fields are bound to public variables in the TypeScript file through the use of [(ngModel)]
tags.
While we have the providers and pages created, they are orphaned in the application. We need to link them all in the project’s src/app/app.module.ts file. Open this file and include the following:
import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { ListPage } from '../pages/list/list';
import { CreatePage } from '../pages/create/create';
import { Database } from '../providers/database';
import { PasswordGenerator } from '../providers/password-generator';
@NgModule({
declarations: [
MyApp,
ListPage,
CreatePage
],
imports: [
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
ListPage,
CreatePage
],
providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}, Database, PasswordGenerator]
})
export class AppModule {}
You’ll notice that we’ve imported the pages and providers and injected them into the @NgModule
block. With everything linked, the project can be built and run.
At this point the application should be ready for testing in a device or simulator. If you’ve been following along, execute the following from your Terminal or Command Prompt:
ionic run android
The above command will run the project on Android. If you’d like to run on iOS, just replace the platform android
with ios
.
If you didn’t follow along, but would like to see the application in action, you can download the source code to this project here. Getting the source code up and running is a little different because I’ve stripped out a lot of dependencies to reduce the file size.
npm install
ionic state restore
Executing the above commands on the downloaded project will install all the dependencies, plugins, and platforms at which point you can run the project as if you were following along.
We just saw how to create a time-based one-time password manager using Ionic 2 and Angular. Some cool things about this project was the footer that had a countdown in it. Being able to have a timer like this is useful in projects beyond the one-time password genre. As I mentioned previously, a lot of the code you saw was used in my OTP Safe application that I built with Ionic Framework 1 and AngularJS.
Want to take this application to the next level? It is bad practice to store the secret codes as plain text in the database. Instead you should encrypt them via some kind of pass code. Have you seen the tutorial regarding building a password manager? This tutorial shows how to encrypt text with AES ciphers for safe storage.
If you’ve downloaded the source code to this project, I’d like to request that you do not share it. I spent a lot of time coming up with the source code and project for my premium members.