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

Build A Password Manager For Android And iOS Using Ionic 2

Have you ever wanted to build your own password manager? Maybe you don’t trust the password management tools that already exist, or maybe you just want the experience. I personally use the tool 1Password, but many of my friends don’t trust the applications that exist on the market. It is a valid concern that they have. What if the password managers that exist are using an ancient or obsolete DES specification or similar, rather than the modern AES? What if the master passwords are not being hashed with a strong Bcrypt algorithm?

Not too long ago we created a password manager using the NativeScript framework, but what if we wanted to build one with a different technology.

We’re going to see how to build a password manager that makes use of the AES specification using Ionic 2 and Angular. This application will work for both Android and iOS and look great on both.

Before we get too far in the development process, it is probably a good idea to see what we’re going to build. In the following screenshots you’ll see that we are creating a mobile application with five screens. There will be a screen for unlocking the application, resetting the master password, listing saved passwords, creating passwords, and viewing passwords.

Ionic 2 Password Manager

In this particular example we won’t worry about editing or changing existing passwords. Let it be a challenge for yourself after completing this tutorial.

The Requirements

There are a few requirements that must be met before we start developing the application:

  • Node.js 4.0+
  • The Ionic 2 CLI
  • The Android SDK, Xcode, or both installed and configured

We need Node.js because it ships with the Node Package Manager (NPM) which we’ll need for installing various packages and dependencies throughout this tutorial. To develop with Ionic 2 we need the Ionic 2 CLI. It is important to note that the Ionic 1 CLI will not work for us. Finally, the Android SDK is required for building Android applications and Xcode is required for building iOS applications.

Creating a New Ionic 2 Android and iOS Project

To make this project possible, we should probably start by creating a fresh Android and iOS project to work with. Using your Terminal (Mac and Linux) or Command Prompt (Windows), execute the following:

ionic start ionic-password-manager blank --v2

The above command will create a new Ionic 2 project that uses Angular and TypeScript, hence the --v2 flag.

The next thing we need to worry about is adding our build platforms. Navigate into the freshly created project using your Command Prompt or Terminal and execute the following:

ionic platform add ios
ionic platform add android

If you’re not using a Mac, you cannot build for the iOS platform, but regardless of operating system you’ll be able to build for Android, provided you’ve installed the Android SDK.

Installing Necessary the Plugins and Dependencies

There are a few dependencies that must be installed for this project to be a success. For example, all of our data will be saved in a SQLite database. This means we must include a SQLite plugin. We’ll also be including libraries that offer the various hashing and ciphertext support.

Starting with the SQLite plugin, execute the following from your Terminal or Command Prompt while inside the project root:

ionic plugin add cordova-sqlite-storage

The next dependency to worry about is the Bcrypt library that will hash our master password to keep it safe from malicious users. We’re going to be using BcryptJS and it can be installed by executing the following:

npm install bcryptjs --save

Because we are using TypeScript, we have two options when it comes to using the BcryptJS JavaScript library. We can either disable the type definition requirement on the library, or download the missing type definitions. Although more work, it is a more attractive solution to download the type definitions.

Install the Missing Type Definitions for BcryptJS

Because we’re putting in the extra effort, we’re going to include the missing type definitions for BcryptJS. It is probably a good idea to note that they are not missing, it is just that BcryptJS is a JavaScript library, not necessarily prepared for TypeScript development.

From the Command Prompt or Terminal, execute the following:

npm install @types/bcryptjs --save

At this point the BcryptJS library can be used in the project with TypeScript type definition support.

Adding the Forge Ciphertext Dependency

The last dependency we need to worry about, node-forge, is for encrypting and decrypting password items. This dependency can be installed with NPM, but at the time of writing this, a special version must be used.

From the Command Prompt or Terminal, execute the following:

npm install git://github.com/digitalbazaar/forge.git#ef835fafe --save

In the above command we are installing commit ef835fafe because it supports Webpack which is what Ionic 2 uses to build projects. In Forge 7.0 Webpack will be supported in full, but until then we have to use the development build.

There is no good set of type definitions available for Forge, so we’ll proceed without them.

Wew! All of our plugins and dependencies should be ready to go at this point. Now we can do the actual development required for this password management application.

Understanding the SQLite Data Model

Since we’re going to be using SQLite to store our data, it is probably a good idea to get a sense of our data model. In this particular password management application, it is a good idea to have two different tables of data.

The first table we create should store the master password and any information used to unlock the application. Let’s call this table master and assume it will have the following schema:

idpassword
integertext

The only thing to be stored in the above table is a hashed copy of the password, used for validation.

The second table we create should store a single password item and any information associated with that password item. Let’s call this table passwords plural because many password items will exist in this table. Assume it has the following schema:

idtitleusernamepasswordsaltiv
integertexttexttexttexttext

In the above table we plan to store a plaintext password title, an encrypted username, an encrypted password, and the salt and initialization vector used in the encryption process.

The data stored will look something like below:

Ionic Password Manager SQLite Data

This application is simple so we don’t have to worry about any data relationships in our SQLite database. At this point we’re ready to start developing.

Creating the SQLite Database Shared Provider

Since we were just talking about the database, it makes sense for it to be the first thing we start developing. We’re going to create a shared database provider. This can often be referred to as a singleton class, a shared service, or even a global service. We’re doing this because we want all interactions with our database to happen in a single file, not mashed in with each of our application pages.

To create this provider, execute the following from the root of your project:

ionic g provider database

The above command will generate the provider at src/providers/database.ts. By default it comes with a bunch of code that is pretty useless to use. We are going to wipe it out and replace it with the following:

import { Injectable } from '@angular/core';
import {SQLite} from 'ionic-native';

@Injectable()
export class Database {

    private storage: SQLite;
    private isOpen: boolean;

    public constructor() {
        if(!this.isOpen) {
            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, username TEXT, password TEXT, salt TEXT, iv TEXT)", []);
                this.storage.executeSql("CREATE TABLE IF NOT EXISTS master (id INTEGER PRIMARY KEY AUTOINCREMENT, password TEXT)", []);
                this.isOpen = true;
            });
        }
    }

    public getPasswords() {
        return new Promise((resolve, reject) => {
            this.storage.executeSql("SELECT * FROM passwords ORDER BY title", []).then((data) => {
                let passwords = [];
                if(data.rows.length > 0) {
                    for(let i = 0; i < data.rows.length; i++) {
                        passwords.push({
                            id: data.rows.item(i).id,
                            title: data.rows.item(i).title,
                            username: data.rows.item(i).username,
                            password: data.rows.item(i).password,
                            salt: data.rows.item(i).salt,
                            iv: data.rows.item(i).iv
                        });
                    }
                }
                resolve(passwords);
            }, (error) => {
                reject(error);
            });
        });
    }

    public getPasswordById(passwordId: number) {
        return new Promise((resolve, reject) => {
            this.storage.executeSql("SELECT * FROM passwords WHERE id = ?", [passwordId]).then((data) => {
                let password = {};
                if(data.rows.length > 0) {
                    password = {
                        id: data.rows.item(0).id,
                        title: data.rows.item(0).title,
                        username: data.rows.item(0).username,
                        password: data.rows.item(0).password,
                        salt: data.rows.item(0).salt,
                        iv: data.rows.item(0).iv
                    };
                }
                resolve(password);
            }, (error) => {
                reject(error);
            });
        });
    }

    public createPassword(item: any) {
        return new Promise((resolve, reject) => {
            this.storage.executeSql("INSERT INTO passwords (title, username, password, salt, iv) VALUES (?, ?, ?, ?, ?)", [item.title, item.username, item.password, item.salt, item.iv]).then((data) => {
                resolve(data);
            }, (error) => {
                reject(error);
            });
        });
    }

    public deletePasswords() {
        return new Promise((resolve, reject) => {
            this.storage.executeSql("DELETE FROM passwords", []).then((data) => {
                resolve(data);
            }, (error) => {
                reject(error);
            });
        });
    }

    public getMaster() {
        return new Promise((resolve, reject) => {
            this.storage.executeSql("SELECT * FROM master LIMIT 1", []).then((data) => {
                let master = {};
                if(data.rows.length > 0) {
                    for(let i = 0; i < data.rows.length; i++) {
                        master = {
                            id: data.rows.item(i).id,
                            password: data.rows.item(i).password
                        };
                    }
                }
                resolve(master);
            }, (error) => {
                reject(error);
            });
        });
    }

    public createMaster(item: any) {
        return new Promise((resolve, reject) => {
            this.storage.executeSql("INSERT INTO master (password) VALUES (?)", [item.password]).then((data) => {
                resolve(data);
            }, (error) => {
                reject(error);
            });
        });
    }

    public deleteMaster() {
        return new Promise((resolve, reject) => {
            this.storage.executeSql("DELETE FROM master", []).then((data) => {
                resolve(data);
            }, (error) => {
                reject(error);
            });
        });
    }

}

So what exactly is happening in the above source code?

The first thing we’re doing is importing a few classes. This provider is going to be something we can inject, so we want to import the Injectable class. We are also going to take advantage of Ionic Native, so the SQLite class is necessary. Ionic Native will make the SQLite plugin very Angular friendly.

This brings us into the class itself.

The constructor method is where we initialize Ionic Native and create our two tables if they do not already exist, as seen in the following SQL statements:

CREATE TABLE IF NOT EXISTS passwords (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, username TEXT, password TEXT, salt TEXT, iv TEXT);
CREATE TABLE IF NOT EXISTS master (id INTEGER PRIMARY KEY AUTOINCREMENT, password TEXT);

The constructor method is fired every time we inject this provider. Because we use the IF NOT EXISTS text in our SQL query, the creation queries will be ignored if the tables already exists. We are also setting an isOpen variable to prevent creating multiple database instances.

The rest of our Database class can be thought of as two different parts. We have the part for retrieving, creating, and deleting the master password and then we have the part for retrieving, creating, and deleting the password items.

Let’s do things a bit out of order here, starting with the creation of any password item:

public createPassword(item: any) {
    return new Promise((resolve, reject) => {
        this.storage.executeSql("INSERT INTO passwords (title, username, password, salt, iv) VALUES (?, ?, ?, ?, ?)", [item.title, item.username, item.password, item.salt, item.iv]).then((data) => {
            resolve(data);
        }, (error) => {
            reject(error);
        });
    });
}

We’re working with JavaScript objects, but saving with SQL so in the above createPassword function we are taking a JavaScript object and doing conversions. This way we don’t have to worry about writing SQL in each of our application pages. Upon successful creation, the row id is returned.

Now let’s say we want to retrieve all the passwords that were saved:

public getPasswords() {
    return new Promise((resolve, reject) => {
        this.storage.executeSql("SELECT * FROM passwords ORDER BY title", []).then((data) => {
            let passwords = [];
            if(data.rows.length > 0) {
                for(let i = 0; i < data.rows.length; i++) {
                    passwords.push({
                        id: data.rows.item(i).id,
                        title: data.rows.item(i).title,
                        username: data.rows.item(i).username,
                        password: data.rows.item(i).password,
                        salt: data.rows.item(i).salt,
                        iv: data.rows.item(i).iv
                    });
                }
            }
            resolve(passwords);
        }, (error) => {
            reject(error);
        });
    });
}

We don’t want to run a query and have to work with a row of data in our application pages. Instead we can do our data manipulation in the Database provider and return an array of objects. This is much more pleasant to work with. So for every row returned in the query, we take the columns and map them to an object property. At the end, we push the objects into an array.

Pretty much the same thing is done if we want to get a particular password by its id. In that scenario we just return a single object rather than an array.

Deleting passwords isn’t too much different than we’ve already seen. In fact it is most similar to creating passwords with the exception we are using a DELETE instead of an INSERT:

public deletePasswords() {
    return new Promise((resolve, reject) => {
        this.storage.executeSql("DELETE FROM passwords", []).then((data) => {
            resolve(data);
        }, (error) => {
            reject(error);
        });
    });
}

Creating the master password is a lot easier since there is only one of them. The queries however are exactly the same as if there were many of them.

This Database provider will be used in every application page. You’ll see how clean the project becomes after making a provider out of it.

Creating the Forge Ciphertext Shared Provider

We will be working with a second provider. We will be creating a ForgeInstance provider that will be responsible for encrypting and decrypting password items. Because the Forge JavaScript library is more complex than BCryptJS, it is best wrapped as a provider.

Using the Ionic generator like we did for the database provider, let’s create the provider for Forge. From the Command Prompt or Terminal, execute the following:

ionic g provider forge

The above command will create an src/providers/forge.ts file. Open the file and replace everything with the following code:

import { Injectable } from '@angular/core';
import Forge from 'node-forge';

@Injectable()
export class ForgeInstance {

    public constructor() { }

    public generateSalt() {
        return Forge.util.encode64(Forge.random.getBytesSync(128));
    }

    public generateIv() {
        return Forge.util.encode64(Forge.random.getBytesSync(16));
    }

    public encrypt(message: string, password: string, salt: any, iv: any) {
        let key = Forge.pkcs5.pbkdf2(password, Forge.util.decode64(salt), 4, 16);
        let cipher = Forge.cipher.createCipher('AES-CBC', key);
        cipher.start({iv: Forge.util.decode64(iv)});
        cipher.update(Forge.util.createBuffer(message));
        cipher.finish();
        return Forge.util.encode64(cipher.output.getBytes());
    }

    public decrypt(cipherText: string, password: string, salt: string, iv: string) {
        let key = Forge.pkcs5.pbkdf2(password, Forge.util.decode64(salt), 4, 16);
        let decipher = Forge.cipher.createDecipher('AES-CBC', key);
        decipher.start({iv: Forge.util.decode64(iv)});
        decipher.update(Forge.util.createBuffer(Forge.util.decode64(cipherText)));
        decipher.finish();
        return decipher.output.toString();
    }

}

We won’t get any autocomplete and other cool type definition features because we’re not using any type definitions for the Forge library.

In the ForgeInstance class we have four functions. Two for generating the necessary salt and initialization vector components and two for actually performing the encryption or decryption.

public encrypt(message: string, password: string, salt: any, iv: any) {
    let key = Forge.pkcs5.pbkdf2(password, Forge.util.decode64(salt), 4, 16);
    let cipher = Forge.cipher.createCipher('AES-CBC', key);
    cipher.start({iv: Forge.util.decode64(iv)});
    cipher.update(Forge.util.createBuffer(message));
    cipher.finish();
    return Forge.util.encode64(cipher.output.getBytes());
}

In the above encrypt function we are expecting a string to be encrypted, a password to encrypt with which in our case will be the master password, a random salt and a random initialization vector. When the encryption process completes we will be returned a ciphertext string.

public decrypt(cipherText: string, password: string, salt: string, iv: string) {
    let key = Forge.pkcs5.pbkdf2(password, Forge.util.decode64(salt), 4, 16);
    let decipher = Forge.cipher.createDecipher('AES-CBC', key);
    decipher.start({iv: Forge.util.decode64(iv)});
    decipher.update(Forge.util.createBuffer(Forge.util.decode64(cipherText)));
    decipher.finish();
    return decipher.output.toString();
}

The decrypt function will take pretty much the same information, but instead of a plaintext message we’ll pass the ciphertext. Plaintext will be returned after a successful decryption.

More information on encryption and decryption with the Forge library can be seen in a previous post I wrote on the subject.

Bootstrapping the Shared Providers for Global Use

We want to be able to use our two providers on any page as global classes. To do this we need to inject them in our driving TypeScript file. Open the project’s src/app/app.module.ts file and include the following code:

import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { Database } from '../providers/database';
import { ForgeInstance } from '../providers/forge';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}, Database, ForgeInstance]
})
export class AppModule {}

In the above code you’ll notice that we are importing the Database and ForgeInstance providers and then injecting them in the providers list found in the @NgModule chunk. Doing this sets us up for success in each of our application pages.

Jumping ahead, we should probably swap out the default page with our login page. Open the project’s src/app/app.component.ts file and include the following:

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar, Splashscreen } from 'ionic-native';

import { LoginPage } from '../pages/login/login';

@Component({
    templateUrl: 'app.html'
})
export class MyApp {

    rootPage = LoginPage;

    constructor(platform: Platform) {
        platform.ready().then(() => {
        StatusBar.styleDefault();
        Splashscreen.hide();
        });
    }

}

You’ll notice that I’ve swapped out HomePage with our soon to be created LoginPage. Beyond that, nothing really had to be done in this file.

Building the Page for Unlocking the Password Manager

Now we can dive into the development of each of our application pages. We’re going to develop them in the order that they are viewed by the end user. This means we’re going to start by creating the page for unlocking the application, otherwise known as the LoginPage. Feel free to create your pages in any order you want.

To create our page for unlocking the application, we’re going to use the Ionic generator again. From the Command Prompt or Terminal, execute the following:

ionic g page login

The above command will create a set of files found in the src/pages/login directory. These files include an HTML UI file, an SCSS styling file, and a TypeScript logic file.

Let’s start by creating our page logic. Open the project’s src/pages/login/login.ts file and include the following code:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Database } from "../../providers/database";
import { ListPage } from "../list/list";
import { ResetPage } from "../reset/reset";
import * as BcryptJS from "bcryptjs";

@Component({
    templateUrl: 'login.html',
})
export class LoginPage {

    public password: string;

    public constructor(private navCtrl: NavController, private database: Database) { }

    public ionViewDidEnter() {
        this.password = "";
    }

    public validate() {
        if(this.password != "") {
            this.database.getMaster().then((result: any) => {
                if(BcryptJS.compareSync(this.password, result.password)) {
                    this.navCtrl.push(ListPage, {password: this.password});
                }
            }, (error) => {
                console.log("ERROR: " + error);
            });
        }
    }

    public reset() {
        this.navCtrl.push(ResetPage);
    }

}

Time to break down what is happening in the above code.

This particular page will be using the database provider as well as the BcryptJS library, so we need to import them. We’ll also be able to navigate to the password list page and the page for resetting the master password from here. Even though we haven’t created them yet, we’re going to import them.

In the constructor method of our LoginPage we inject a few dependencies. This allows us to use them in other functions throughout the page.

public ionViewDidEnter() {
    this.password = "";
}

We are including the above ionViewDidEnter method to reset the input field when we navigate into this page. This method will be triggered in back or pop events as well. We don’t do this in the constructor as it doesn’t trigger on back or pop events.

public validate() {
    if(this.password != "") {
        this.database.getMaster().then((result: any) => {
            if(BcryptJS.compareSync(this.password, result.password)) {
                this.nav.push(ListPage, {password: this.password});
            }
        }, (error) => {
            console.log("ERROR: " + error);
        });
    }
}

In the above validate method we check to make sure the password input field has been filled. If it has been filled then we can obtain the master password through the database provider that we previously created. With the asynchronous result we can use the BcryptJS library to compare our plain text master password with the hashed version of the password. If they match we can unlock our application and navigate into it. From this point, the master password will be passed around internally in our application.

Finally we have the reset method that will navigate us to the page for resetting our master password and wiping all data.

With the TypeScript logic out of the way we can focus on the HTML markup. Open the project’s src/pages/login/login.html file and include the following markup:

<ion-header>
    <ion-navbar>
        <ion-title>Password Manager</ion-title>
    </ion-navbar>
</ion-header>

<ion-content padding class="login">
    <ion-list>
        <ion-item>
            <ion-label floating>Password</ion-label>
            <ion-input type="password" [(ngModel)]="password"></ion-input>
        </ion-item>
    </ion-list>
    <button ion-button (click)="validate()">Unlock</button>
    <button ion-button color="danger" (click)="reset()">Reset</button>
</ion-content>

In the above markup you’ll notice that our form is a list. The input field uses [(ngModel)] which is bound to the public password variable found in our TypeScript file. This means you can access the data of the form field through that variable.

When the button tags are clicked, the appropriate functions will be called from the TypeScript file.

Building the Page for Resetting the Master Password

With the unlock page out of the way, we can focus on the page for creating our master password and wiping all the data. When it comes to Bcrypt, we’re creating a hash which cannot be decrypted. This means that there is no way to recover our master password used in password item encryption. If we forget or lose the password, the database must be cleared and the master password must be reset.

Let’s start by generating this page. From the Terminal or Command Prompt, execute the following command:

ionic g page reset

The above command will create a few files in the project’s src/pages/reset directory. Just like with the previous page, we’re going to start editing the TypeScript file.

Open the project’s src/pages/reset/reset.ts file and include the following code:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Database } from "../../providers/database";
import * as BcryptJS from "bcryptjs";

@Component({
    templateUrl: 'reset.html',
})
export class ResetPage {

    public password: string;
    public confirmation: string;

    constructor(private navCtrl: NavController, private database: Database) {}

    public reset() {
        if(this.password != "" && this.confirmation != "") {
            if(this.password == this.confirmation) {
                this.database.deletePasswords().then((result) => {
                    return this.database.deleteMaster();
                }).then((result) => {
                    this.database.createMaster({
                        "password": BcryptJS.hashSync(this.password, 8)
                    }).then((result) => {
                        this.navCtrl.pop();
                    }, (error) => {
                        console.log("ERROR: ", error);
                    });
                }, (error) => {
                    console.log("ERROR: ", error);
                });
            }
        }
    }

}

Just like in the previous page we’re importing the database provider and BcryptJS library. Inside the ResetPage class we have two public variables which will both be bound to input form elements in the UI. In the constructor method we inject the dependencies to be used in other functions. So far nothing new here.

This brings us to the reset method:

public reset() {
    if(this.password != "" && this.confirmation != "") {
        if(this.password == this.confirmation) {
            this.database.deletePasswords().then((result) => {
                return this.database.deleteMaster();
            }).then((result) => {
                this.database.createMaster({
                    "password": BcryptJS.hashSync(this.password, 8)
                }).then((result) => {
                    this.navCtrl.pop();
                }, (error) => {
                    console.log("ERROR: ", error);
                });
            }, (error) => {
                console.log("ERROR: ", error);
            });
        }
    }
}

First we check to make sure that both the password and confirmation password are not blank. Then we check to make sure they match. Provided this is true we can chain two methods from the database provider to delete all the password items and delete the master password. We’re also chaining a third promise event for creating a new master password. More information on promises can be found in a great article by Nolan Lawson.

Provided we make it to the final link in the chain, we’ll take the new master password, hash it, and store it. After it has been saved we can navigate back to the unlock page.

Time to create the HTML UI that is paired with this particular TypeScript file. Open the project’s src/pages/reset/reset.html file and include the following markup:

<ion-header>
    <ion-navbar>
        <ion-title>Reset Master Passcode</ion-title>
    </ion-navbar>
</ion-header>

<ion-content padding class="reset">
    <ion-list>
        <ion-item>
            <ion-label floating>Master Passcode</ion-label>
            <ion-input type="password" [(ngModel)]="password"></ion-input>
        </ion-item>
        <ion-item>
            <ion-label floating>Confirm Master Passcode</ion-label>
            <ion-input type="password" [(ngModel)]="confirmation"></ion-input>
        </ion-item>
    </ion-list>
    <button danger (click)="reset()">Reset / Wipe</button>
</ion-content>

Like with the previous page we have a list of form elements, each bound to a public variable using the [(ngModel)] tag. The reset button, when clicked will execute our reset method.

Building the Page for Listing Saved Passwords

With the master password pages out of the way, let’s start working on the pages for managing individual password items. When we unlock the application we will be placed on a page with a list of password items. Let’s create that page by entering the following into our Terminal or Command Prompt:

ionic g page list

The above command will generate files in the project’s src/pages/list directory. A TypeScript, HTML, and SCSS file will be created just like in the previous pages.

Starting with the TypeScript file, open the project’s src/pages/list/list.ts file and include the following code:

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Database } from "../../providers/database";
import { ViewPage } from "../view/view";
import { CreatePage } from "../create/create";

@Component({
    templateUrl: 'list.html',
})
export class ListPage {

    public passwordList: Array<Object>;

    public constructor(private navCtrl: NavController, private navParams: NavParams, private database: Database) {
        this.passwordList = [];
    }

    public ionViewDidEnter() {
        this.database.getPasswords().then((results) => {
            this.passwordList = <Array<Object>>results;
        }, (error) => {
            console.log("ERROR: ", error);
        });
    }

    public viewDetails(item: any) {
        this.navCtrl.push(ViewPage, {password: this.navParams.get("password"), id: item.id});
    }

    public create() {
        this.navCtrl.push(CreatePage, {password: this.navParams.get("password")});
    }

}

Like we’ve already seen, we’re importing the Database component as well as two pages that we can navigate to. These pages include the soon to be created ViewPage and CreatePage.

In the ListPage class we have a public variable that will hold our password items and be bound to the list in the UI. In the constructor method, not only do we inject a few components, but we also initialize the array.

This brings us to the ionViewDidEnter method which triggers after the constructor method. This is where we’ll query for all of the available password items.

public ionViewDidEnter() {
    this.database.getPasswords().then((results) => {
        this.passwordList = <Array<Object>>results;
    }, (error) => {
        console.log("ERROR: ", error);
    });
}

The results of this asynchronous call against the database will be cast as an Array<Object> and be loaded into our public variable.

This leaves us with the two navigation methods. Starting with the viewDetails method:

public viewDetails(item: any) {
    this.navCtrl.push(ViewPage, {password: this.navParams.get("password"), id: item.id});
}

In the above method we navigate to the soon to be created ViewPage. What is interesting here is we are continuing to pass around the master password in addition to a particular password id. This is because this method is called after clicking on a particular password in the list. When clicked we pass the id so we can expand it on the next page.

The create method is a bit simpler because we’re only passing the master password.

Now we can have a look at the HTML file that is paired with this TypeScript file. Open the project’s src/pages/list/list.html file and include the following markup:

<ion-header>
    <ion-navbar>
        <ion-title>Password List</ion-title>
        <ion-buttons end>
            <button ion-button (click)="create()">Create</button>
        </ion-buttons>
    </ion-navbar>
</ion-header>

<ion-content padding class="list">
    <ion-list>
        <ion-item *ngFor="let item of passwordList" (click)="viewDetails(item)">
            {{item.title}}
        </ion-item>
    </ion-list>
</ion-content>

Here we have a button in our navigation bar. When clicked it will call the create method and navigate us to a new page. The core content includes a list. We loop through the public passwordList creating a new list item for each array item. When any of those items are clicked, it is passed to the viewDetails method.

Building the Page for Creating New Passwords

How about creating new password items? Since we put down the foundation for database interaction and ciphertext, this part should be pretty easy. Like with the previous pages, the first step will be to generate the application page. From the Terminal or Command Prompt, execute the following:

ionic g page create

The above command will generate an src/pages/create directory with a TypeScript, HTML, and SCSS file in it. We’re going to be changing the TypeScript file first. Sound familiar?

Open the project’s src/pages/create/create.ts file and include the following code:

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Database } from "../../providers/database";
import { ForgeInstance } from "../../providers/forge";

@Component({
    templateUrl: 'create.html',
})
export class CreatePage {

    public title: string;
    public username: string;
    public password: string;

    constructor(private navCtrl: NavController, private navParams: NavParams, private database: Database, private forg: ForgeInstance) {
        this.title = "";
        this.username = "";
        this.password = "";
    }

    public save() {
        if(this.title != "" && this.username != "" && this.password != "") {
            console.log("TRYING TO SAVE");
            let salt = this.forg.generateSalt();
            let iv = this.forg.generateIv();
            this.database.createPassword({
                "title": this.title,
                "username": this.forg.encrypt(this.username, this.navParams.get("password"), salt, iv),
                "password": this.forg.encrypt(this.password, this.navParams.get("password"), salt, iv),
                "salt": salt,
                "iv": iv
            }).then((result) => {
                console.log("TRYING TO GO BACK");
                this.navCtrl.pop();
            }, (error) => {
                console.log("ERROR: ", error);
            });
        }
    }

}

You’ve heard the same old story before when it comes to importing components so we’re going to skip straight into the CreatePage class.

Inside the constructor method we are going to inject our various providers and services and then initialize all our public variables. These variables will be bound to the UI, in particular an input form.

This brings us to the save method:

public save() {
    if(this.title != "" && this.username != "" && this.password != "") {
        console.log("TRYING TO SAVE");
        let salt = this.forg.generateSalt();
        let iv = this.forg.generateIv();
        this.database.createPassword({
            "title": this.title,
            "username": this.forg.encrypt(this.username, this.navParams.get("password"), salt, iv),
            "password": this.forg.encrypt(this.password, this.navParams.get("password"), salt, iv),
            "salt": salt,
            "iv": iv
        }).then((result) => {
            console.log("TRYING TO GO BACK");
            this.navCtrl.pop();
        }, (error) => {
            console.log("ERROR: ", error);
        });
    }
}

The first thing we want to do is make sure each of the form inputs are not empty. If this condition is met we can generate a new salt and initialization vector to be used in our encryption process. When it comes to saving a new password item we can pass a JavaScript object to the createPassword method of our database provider. The title property of this object will be plain text, but we’ll encrypt every other field.

When the save is successful we’ll navigate backwards in the stack to the page for listing passwords.

Now we’re going to look at the HTML file that is paired with this TypeScript file. Open the project’s src/pages/create/create.html file and include the following markup:

<ion-header>
    <ion-navbar>
        <ion-title>Create Password</ion-title>
        <ion-buttons end>
            <button ion-button (click)="save()">Save</button>
        </ion-buttons>
    </ion-navbar>
</ion-header>

<ion-content padding class="create">
    <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>Username</ion-label>
            <ion-input type="text" [(ngModel)]="username"></ion-input>
        </ion-item>
        <ion-item>
            <ion-label floating>Password</ion-label>
            <ion-input type="password" [(ngModel)]="password"></ion-input>
        </ion-item>
    </ion-list>
</ion-content>

Just like with the other pages, we have a list of form elements each bound to a public variable with the [(ngModel)] tag. When the button is clicked in the navigation bar, the save method will be called.

Building the Page for Viewing Password Details

This brings us to our final application page. We need to create a page for decrypting and viewing password details. From a UI perspective it makes sense to have it look similar to the creation page, but our logic will be slightly different.

From your Command Prompt or Terminal, execute the following to generate the new page:

ionic g page view

The above command will generate an src/pages/view directory with the same three files that we’ve grown to expect.

Starting with the TypeScript file, open your project’s src/pages/view/view.ts file and include the following source code:

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Database } from "../../providers/database";
import { ForgeInstance } from "../../providers/forge";

@Component({
    templateUrl: 'view.html',
})
export class ViewPage {

    public passwordItem: any;

    constructor(private navCtrl: NavController, private navParams: NavParams, private database: Database, private forge: ForgeInstance) {
        this.passwordItem = {};
    }

    public ionViewDidEnter() {
        this.database.getPasswordById(this.navParams.get("id")).then((results: any) => {
            this.passwordItem.title = results.title;
            this.passwordItem.username = this.forge.decrypt(results.username, this.navParams.get("password"), results.salt, results.iv);
            this.passwordItem.password = this.forge.decrypt(results.password, this.navParams.get("password"), results.salt, results.iv);
        }, (error) => {
            console.log("ERROR: ", error);
        });
    }

}

Skipping directly into the ViewPage class, we have our public passwordItem variable which will be bound to the UI. It is initialized in the constructor method along with the provider injections.

This brings us to the ionViewDidEnter method that will populate the public variable:

public ionViewDidEnter() {
    this.database.getPasswordById(this.navParams.get("id")).then((results: any) => {
        this.passwordItem.title = results.title;
        this.passwordItem.username = this.forge.decrypt(results.username, this.navParams.get("password"), results.salt, results.iv);
        this.passwordItem.password = this.forge.decrypt(results.password, this.navParams.get("password"), results.salt, results.iv);
    }, (error) => {
        console.log("ERROR: ", error);
    });
}

Remember, we’re passing around the master password between pages and in this scenario a password id as well. We’re going to use the password id to query for a particular password item. When the item is retrieved, we can fill the public variable. The username and password properties can be decrypted uses the Forge provider.

Time to work on the final HTML file. Open the project’s src/pages/view/view.html and include the following markup:

<ion-header>
    <ion-navbar>
        <ion-title>View Password</ion-title>
    </ion-navbar>
</ion-header>

<ion-content padding class="view">
    <ion-list>
        <ion-list-header>
            TITLE
        </ion-list-header>
        <ion-item>{{ passwordItem.title }}</ion-item>
        <ion-list-header>
            USERNAME
        </ion-list-header>
        <ion-item>{{ passwordItem.username }}</ion-item>
        <ion-list-header>
            PASSWORD
        </ion-list-header>
        <ion-item>{{ passwordItem.password }}</ion-item>
    </ion-list>
</ion-content>

We have a very standard list, but no form elements this time. We’re strictly rendering the public variables to the screen and not doing anything special.

Each of our pages came with an SCSS file. Before we can call our application complete, we should probably load those files into our theme.

Linking the Pages Together

While we created all these pages, they aren’t linked together. In other words we won’t be able to navigate to them quite yet. Each page needs to be added to the @NgModule block like we saw previously with the providers.

Open the project’s src/app/app.module.ts file and include the following:

import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { LoginPage } from '../pages/login/login';
import { CreatePage } from '../pages/create/create';
import { ResetPage } from '../pages/reset/reset';
import { ViewPage } from '../pages/view/view';
import { ListPage } from '../pages/list/list';
import { Database } from '../providers/database';
import { ForgeInstance } from '../providers/forge';

@NgModule({
  declarations: [
    MyApp,
    LoginPage,
    CreatePage,
    ResetPage,
    ViewPage,
    ListPage
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    LoginPage,
    CreatePage,
    ResetPage,
    ViewPage,
    ListPage
  ],
  providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}, Database, ForgeInstance]
})
export class AppModule {}

In the above, we’ve removed the HomePage that came with the default template and included all our pages.

At this point the application should be ready to run!

Testing the Ionic 2 Password Management Application

We did a lot to get to this point, but we’re finally ready to test the application to see it in action. If you’ve been following along with everything so far, execute the following from your Command Prompt or Terminal:

ionic run ios

The above command requires a Mac computer, but if you’re using Windows or Linux, or you’d rather test for Android, execute the following command:

ionic run android

Let’s say you didn’t want to go through all the steps of this incredibly long tutorial, but you still wanted to test out the application. You can download the full project source code here. To run the downloaded project it must first be extracted. Once this is done, navigate into the extracted directory using the Terminal or Command Prompt and execute the following:

npm install
ionic state restore

The above commands will download all the project dependencies and restore the various platforms and plugins. You should be able to run the project at this point.

Conclusion

We just created an epic password management application for Android and iOS using Ionic 2 and Angular. This application covered everything from building shared providers, using SQLite, and password hashing or encryption using Bcrypt and AES specifications. Being that this application was created with Angular, much of the code can be reproduced for the web or other mobile frameworks that support Angular.

If you’d like to download the full source code to the project, it can be found here. I spent a lot of time creating the project and writing this tutorial, so please do not share it without my permission.

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

Subscribe

Subscribe to my newsletter for monthly tips and tricks on subjects such as mobile, web, and game development.

The Polyglot Developer
The Polyglot Developer

Support This Site

Close

Subscribe To Our Newsletter

Stay up to date on the latest in web, mobile, and game development, plus receive exclusive content by subscribing to The Polyglot Developer newsletter.

Unsubscribe at any time without hassle.