Ionic 4 Two-factor Authentication. Part 2

Introduction

In this tutorial, we will add the ability to automatically read the verification code on Android devices. This part of the tutorial is based on the app built in Part 1.

📘

Important pre-condition

Please complete the Ionic 4 Two-factor Authentication. Part 1 tutorial first, then continue with this part.

👍

Important notice

Be informed that when creating an app with Cordova plugins it is important that the needed plug-in be imported before you proceed with the app UI.

Importing cordova-plugin-sms-retriever-manager plug-in

  1. Log in to your Appery.io account and go to the Resources > Cordova plugin tab.
  2. Click Import Cordova plugin and select the From Git tab.
  3. Enter https://github.com/hanatharesh2712/ionic-native-sms-retriever-plugin-master.git in Repository URL field and master in the Repository branch field:
1251
  1. Click the Import plugin button. You will see the plugin on the list of imported plug-ins:
1256

Adding Cordova Dependencies

  1. Go to the Appery.io dashboard and open twoFactorAuthApp, the app created in the first part of the tutorial. Then, go to the Project > App settings > Cordova plugins tab.
  2. Here, select the Imported Cordova plugins tab and mark Device - Cordova SMS Retriever Plugin as checked:
1360

Adding Ionic Native Dependencies

  1. In the Appery.io editor, go to Project > App Settings > Npm modules.
  2. In the Dependency section, click Add. Type @ionic-native/sms-retriever as the Module name and ^5.23.0 as the Version:
1351

Adding App Module Dependencies

  1. Now, navigate to Pages > app and go to its MODULE panel.
  2. In the Imports section, enter the following line of code:
import { SMSRetriever } from '@ionic-native/sms-retriever/ngx';
1694
  1. Then, scroll down and in the NgModule Settings > Providers section enter SMSRetriever:
1567

Save all the changes.

Extending App Logic

  1. Open the CODE panel of the SignUp page.
  2. In the Custom includes section, enter { SmsRetriever } and click the Add button. Type @ionic-native/sms-retriever/ngx for the Path field:
1064
  1. Add a new variable with smsRetriever name of SmsRetriever type. Make sure that the Add DI checkbox is checked:
1103
  1. In the Functions section, create a new function named constructor, of Method type.
  2. Let’s add a mechanism to retrieve the application hash. Hash will be needed to identify our app in SMS. Add the following code to the constructor function:
const self = this;
this.smsRetriever.getAppHash()
    .then((res: any) => {
        self.saveHash(res);
    })
    .catch((error: any) => console.warn(error));
  1. In the Functions section, create a new function named saveHash of Async method type. The function should take one argument — hash:
1635
  1. Paste the following code it the *saveHash function code editor and save the app changes:
const localData = await this.Apperyio.data.getStorage("localData");
await this.Apperyio.data.setStorage("localData", {...localData, hash: hash});
  1. Open the CODE panel of the CodeConfirmation page. Here, in the Custom includes section, enter { SmsRetriever } and click the Add button. Type @ionic-native/sms-retriever/ngx into the Path field:
1165
  1. Add new variable with smsRetriever name of SmsRetriever type. Make sure that the Add DI checkbox is checked:
1119
  1. In the Functions section, create a new function of Method type with constructor name. Finally, add the following code to the constructor function:
const self = this;
this.smsRetriever.startWatching()
    .then((res: any) => {
        // Parcing code from SMS
        let smsCode = res.Message.slice(0, res.Message.lastIndexOf('Your hash'));
        smsCode = smsCode.replace(/\D/g, '');
        self.code = smsCode;
    })
    .catch((error: any) => {
        console.warn(error);
    });

Save the project.

Extending Server Code Logic

  1. Let's move to the Appery.io Server Code tab. Here, from the scripts list, find and open the previously created twoFactorAuthentication server code script.
  2. Copy the updated script, paste it into the code editor, replacing the value of the DB_ID variable with your database API key. Don't forget to save the changes:
var DB_ID = 'YOUR_DB_API_KEY'; // replace with your database API key

try {
    var requestBody = JSON.parse(request.requestBody);
    var username = requestBody.username;
    var password = requestBody.password;
    var phone = requestBody.phone;
    var code = requestBody.code;
    var hash = requestBody.hash || '';
    var result;

    if (!username) throw new Error('Username is required.');
    if (!password) throw new Error('Password is required.');
    if (!phone) throw new Error('Phone number is required.');
    phone = phone.replace(/[-, ]/g, '');

    // User verification step
    if (!code) {
        // Generate code
        result = generateCode(phone, hash);
    } else {
        // Validate code
        result = validateCode(phone, code);
    }

    // User registration step
    if (typeof result === "boolean") {
        if (!result) throw new Error('Verification code is not valid.');

        // Register user
        result = registerUser(username, password, phone);
    }

    response.success(result, "application/json");
} catch (e) {
    response.error({
        status: 'error',
        message: e.message
    }, 403);
}

function generateCode(phone, hash) {
    var code = Math.floor(100000 + Math.random() * 900000);
    var hashText = (hash && hash.length) ? 'Your hash is: ' + hash : '';  
    var codeText = 'Your verification code is: ' + code;
    var message = codeText + ' ' + hashText;
    
    // Send the generated code using the Twilio service
    var smsResult = Twilio.sendSMS(message, phone);
    if (!!smsResult && smsResult.status === 201) {
        var stackResult = getFromStack(phone);

        if (stackResult && stackResult.length) {
            // Update phone number and code for user verification step
            return Collection.updateObject(DB_ID, "TemporaryStack", stackResult[0]._id, {
                "phone": phone,
                "code": code
            });
        } else {
            // Save phone number and code for user verification step
            return Collection.createObject(DB_ID, "TemporaryStack", {
                "phone": phone,
                "code": code
            });
        }
    } else {
        throw new Error('Please check the phone number format');
    }
}

function getFromStack(phone) {
    var params = {
        criteria: {
            "phone": {
                "$eq": phone
            }
        }
    };
    // Search for existing records in the temporary stack
    return Collection.query(DB_ID, "TemporaryStack", params);
}

function validateCode(phone, code) {
    var validationResult = false;
    var stackResult = getFromStack(phone);

    if (stackResult && stackResult.length && stackResult[0].code === code) {
        validationResult = true;
        cleanFromStack(stackResult[0]._id);
    }

    return validationResult;
}

function cleanFromStack(id) {
    Collection.deleteObject(DB_ID, "TemporaryStack", id);
}

function registerUser(username, password, phone) {
    var user = DatabaseUser.signUp(DB_ID, {
        "username": username,
        "password": password,
        "phone": phone
    });
    return user;
}

Let's review the script changes.

  • We've added an additional hash parameter to the skeleton of the script:
var code = requestBody.code;
var hash = requestBody.hash || '';
var result;
  • Also, we've updated execution of the generateCode function by adding an additional parameter with hash name:
// User verification step
if (!code) {
  // Generate code
  result = generateCode(phone, hash);
} else {
  // Validate code
  result = validateCode(phone, code);
}
  • And the last change, we've updated the variables of the generateCode function:
function generateCode(phone, hash) {
    var code = Math.floor(100000 + Math.random() * 900000);
    var hashText = (hash && hash.length) ? 'Your hash is: ' + hash : '';  
    var codeText = 'Your verification code is: ' + code;
    var message = codeText + ' ' + hashText;
  
   // ...
}

App Testing

In the top menu, click the EXPORT button and select Binary. Install the downloaded file on your device to test the application.

🚧

Important note

Automatic code detection will not work on iOS devices. This feature is only supported on Android devices.

Enter any username and password and your phone number. Click the Submit button. When you receive the verification code, the code from the SMS will be automatically passed to the code field. Click the Submit button. If the code is valid, a pop-up with information about completing the registration will appear:

877