Plugin configuration and initialisation

The Rapid security cordova plugin requires variables to be specified on plugin install.

  • RAPID_WHITE_LIST - a list of Two way TLS sites secured using Rapid. Separated using a space or alternatively you can use regular expressions.
  • RAPID_ACCESS_GROUP_IDENTIFIER - iOS access group required for storing credentials in keychain.

Cordova will allow you to install the plugin from your local file system. An example on how to provide the variables is shown below.

cordova plugin add ../cordova-plugin-rapidsecurity 
--variable RAPID_WHITE_LIST="https://rapidportal.intercede.com/authenticate https://rapidportal.intercede.com/account" 
--variable RAPID_ACCESS_GROUP_IDENTIFIER="com.intercede.RapidDemoApp"

On installation of the plugin, a hook script is run to install a couple of javascript files. Both are optional and will depend on your application. The first is iOS specific and is used to setup your keychain access group. The second is an angular javascript wrapper - see later for how to configure this if you require an angular api.

iOS Entitlements

Your keychain access group must be setup either within your own hook script or alternatively by running the hook script provided with the Rapid security plugin. To enable the Rapid security hook script, modify your config.xml to setup the hook to run before_compile as below. The hook script will have no affect on the Android platform.

<hook src="scripts/setup_rapid_keychain_access_group.js" type="before_compile" />

Note: There is a dependency on the node modules bluebird, plist and xcode. The demo application has been tested against the following versions of these dependencies, which will need to be added into your package.json file dependencies list.

    "bluebird": "^3.1.1",
    "plist": "^1.2.0",
    "xcode": "0.8.2"

Support Camera Usage

iOS 10 requires a camera usage description in the apps Info.plist.

One way of doing this is to create a script file e.g. scripts/setup_info-plist.js and reference it in the config.xml as follows.

<platform name="ios">
  <hook src="scripts/setup_info-plist.js" type="before_compile" />
</platform>

For the Cordova sample app, "Bank Corp", the file contents would then be as follows:

var fs    = require('fs');     // nodejs.org/api/fs.html
var plist = require('plist');  // www.npmjs.com/package/plist

var FILEPATH = 'platforms/ios/Bank\ Corp/Bank\ Corp-Info.plist';

module.exports = function (context) {

    var xml = fs.readFileSync(FILEPATH, 'utf8');
    var obj = plist.parse(xml);

    obj.NSCameraUsageDescription = 'scan barcode';

    xml = plist.build(obj);
    fs.writeFileSync(FILEPATH, xml, { encoding: 'utf8' });

};

Android permissions

On Android, permissions to external storage and fingerprint access will be required. The plugin will add these permissions automatically on installation.

Cordova setup

To make sure the Rapid security cordova plugin security object is accessible by your javascript in your cordova app you need to make sure you wait for the deviceready event. This event is blocked until all cordova plugins and their associated javascript modules have been loaded which means once it's fired the Rapid security cordova plugin is ready to use.

The easiest way to do this is to attach an onload event to your main index.html page.

<body onload="onLoad()">

With the following javascript in your app.js file.


function onLoad()
{
    document.addEventListener("deviceready", app.OnDeviceReady, false)
}

This attaches an event listener waiting on the deviceready event and will be fired once the plugins have been loaded. In the below example we check if the device is registered and based on the response given by the callback set the corresponding value in the UI.


var app = {

    OnDeviceReady: function() {       

        security.isDeviceRegistered(function(response)
        {
           var outputIsDeviceRegistered = document.getElementById("deviceRegisteredContainer");

           if(deviceRegistered)
           {
                outputIsDeviceRegistered.innerText = "Device is Registered.";
           }
           else
           {
                outputIsDeviceRegistered.innerText = "Device not Registered.";
           } 
        });        
    }
};

Angular setup

The Rapid security cordova plugin includes an angular wrapper. Once you have successfully installed the Rapid security cordova plugin you will find the angular plugin in your applications ionic lib directory. i.e. www\lib\ngRapid\ngRapid.js. To use the Rapid security angular wrapper, make the following modifications to your application.

Modify your index.html to include ngRapid.js like so.

<script src="lib/ngRapid/ngRapid.js"></script>

Modify your app.js to use the ngRapid andular module.

angular.module('starter', ['ionic', 'ngRapid', ...])

On loading the module, your controller can get access to the Rapid security library via dependency injection. Include $rapidSecurity in your controller declaration as shown below.

.controller('YourAppCtrl', ['$scope', '$state', '$ionicPlatform', '$rapidSecurity', ...,
 function($scope, $state, $ionicPlatform, $rapidSecurity, ...) {

The angular samples below will assume they are being implemented in a controller with dependency injection resolved through the controller declaration.

Before making calls during initialisation - it is advised to make sure the plugin has been loaded within the ionic framework. You can do this by wrapping your Rapid security method calls using $ionicPlatform.ready(function() {.

Methods

This section outlines the methods available in the Cordova plugin.


enteringBackground

The enteringBackground method will apply any RapID backgrounding policies you may have setup. Register an onPause and onResume event handler to catch native platform events raised when the application is backgrounded and when returning to the foreground.

platform_icon cordova sample

var app = {
    // Application Constructor
    OnDeviceReady: function() {       
        //...    
        document.addEventListener("pause", app.onPause, false);
        //...
    },

    onPause: function() { 
        security.enteringBackground();
    },
    //...
 };

function onLoad()
{
    document.addEventListener("deviceready", app.OnDeviceReady, false)
}

platform_icon angular sample

//inside your .controller
document.addEventListener("pause", 
  function() {
    $ionicPlatform.ready(function() {           
          $rapidSecurity.enteringBackground()
          .then(function (val) {
            $scope.version = val;
          })
          .catch(function(e) {
            alert(e);
          });
        });
    }, 
false);

Android activity lifecycle

The Android native platform will fire the onPause event when another activity in the same app pushes the Cordova activity into the background. This will mean the onPause event will also be fired when starting the barcode scanner.

Intercede recommend that even in this scenario enteringBackground is still called everytime onPause is triggered.

Android Barcode scanner workaround

In the event you do not want call RapidSecurity.enteringBackground() when starting the scanner, there is only a partial solution on Android.

Starting the barcode scanner on Android will cause the Cordova activity to be pushed in to the background. One way to resolve this is to track the scanner being started and stopped using a global flag.

  1. Create a global flag openingScanner and set to false.
  2. Before starting scanner set the flag to true.
  3. Check this flag in the onPause handler and when the flag is set, do not call enteringBackground.
  4. Unset flag after scanning barcode.

This is a partial solution for Android, because onPause event for the cordova app will not fire in the scenario when the app is backgrounded while the scanner is the current activity. The secure approach is to call enteringBackground everytime onPause is triggered.


identityExists

platform_icon cordova sample

var app = {

//...

    identityExists: function()
    {
        security.identityExists(containerLabel, function(response)
        {
            var message = response == 1 ? "Identity Exists" : "Identity does not exist";
            alert(message);
        });
    },

//...

platform_icon angular sample

//inside your .controller

//...

$scope.isRegistered = false; //will be set properly using checkRegistered once ionic plaform ready

//...

$scope.checkRegistered = function() {
    $ionicPlatform.ready(function() {           
        $rapidSecurity.identityExists("RapidContainer")
            .then(function (registered) {
                $scope.isRegistered = registered;
                $scope.$apply();
            })
            .catch(function(e) {
                alert(e);
                $scope.isRegistered = false;
                $scope.$apply();
            }
        );
    });
}

collectIdentity

platform_icon cordova sample

var app = {

//...

    collectIdentity: function()
    {
        var requestId = /* the request identifier sent from your server */
        security.collectIdentity(containerLabel, requestId, function(response)
        {
            alert("Identity Successfully Collected");
        },
        function(response)
        {
            var message = security.states().Text(response);
            alert(message);
        });
    },

//...

platform_icon angular sample

//inside your .controller

$scope.startRegistration = function() {   
    var registerUrl = "https://myapplication.com/registerUser";  
    $http.post( registerUrl, { SubjectName: $stateParams.logonName }
    ).then(function(response) {
        //The response back from your server needs to be include the requestId for collectIdentity
        return $rapidSecurity.collectIdentity("RapidContainer", response.data.Identifier);
    }).then(function(response) {
        var errorString = rapidStates.Text(error);
        $ionicPopup.alert({title:"Error", template:errorString});
    }).catch(function(error) {

        $timeout(function() { //can be too quick! Pause, so any transitions back are smooth        
        var rapidStates = $rapidSecurity.states();
        var errorString = rapidStates.Text(error);
        if(error == rapidStates.RapidErrorUnknown) {
            errorString = "Please check your network connectivity";
        }            
        $ionicPopup.alert({title:"Error", template:errorString});
        }, 500);

    });
};


removeIdentity

platform_icon cordova sample

var app = {

//...

    removeIdentity: function()
    {
        security.removeIdentity(containerLabel, function(response)
        {
            var message = response == 1 ? "Identity Removed" : "Error";
            alert(message);
        });
    },

//...

platform_icon angular sample

//inside your .controller

$scope.removeIdentity = function() {
    $rapidSecurity.removeIdentity("RapidContainer")
    .then(function() {
        $state.go('welcome'); //go back to welcome view or some other view
    });
}

signData

platform_icon cordova sample

var app = {

//...

    signData: function()
    {
        var data = "data to sign";
        var authenticate = ""; // empty string defaults to RapidUseCache, or pass in "RapidForce" to always enter pin
        security.signData(containerLabel, data, authenticate, function(response)
        {
            alert("Successfully Signed Data." + "{signedData: response}");
        },
        function(response)
        {
            var message = security.states().Text(response);
            alert(message);
        })
    },
//...

 }])    

platform_icon angular sample

//inside your .controller

$scope.signData = function() {
    var label = "RapidContainer";
    var data = "data to sign";
    var authenticate = ""; // empty string defaults to RapidUseCache, or pass in "RapidForce" to always enter pin
    $rapidSecurity.signData(label, data, authenticate)
    .then(function(response) {  
        $state.go('tab.signdata', {signedData: response});
    });
}


unregisterDevice

platform_icon cordova sample

var app = {

//...

    unregisterDevice: function()
    {
      security.unregisterDevice(function()
      {
         alert("Device Successfully Unregistered"); 
      });
    },

//...

platform_icon angular sample

//inside your .controller

$scope.eraseAccount = function() {
    $rapidSecurity.unregisterDevice()
    .then(function() {
        $state.go('welcome'); //go back to welcome view
    });
}

isDeviceRegistered

platform_icon cordova sample

var app = {

//...

    isDeviceRegistered: function() {

       security.isDeviceRegistered(function(response)
       {
           if(response)
           {
               alert("Device is registered");
           }
           else
           {
               alert("Device is not registered");
           }
       });
    },

//...

platform_icon angular sample

//inside your .controller

//...

$scope.isRegistered = false;

//...

$scope.checkRegistered = function() {
    $ionicPlatform.ready(function() {           
        $rapidSecurity.isDeviceRegistered()
            .then(function (registered) {
                $scope.isRegistered = registered;
                $scope.$apply();
            })
            .catch(function(e) {
                alert(e);
                $scope.isRegistered = false;
                $scope.$apply();
            }
        );
    });
}

verifyData

platform_icon cordova sample

var app = {

//...

    verifyData: function() {

       var dataToVerify = /*data output from rapid security signData method */
       security.verifyData(dataToVerify, function(response)
       {
           if(response == 1) alert("signed data verified");
           else alert("failed to verify signed data");
        },
        function(errorResponse)
        {
            alert(errorResponse);
        });
    },

//...

platform_icon angular sample

//inside your .controller

$scope.verifyData = function(){
    var dataToVerify = /*data output from rapid security signData method */
    $rapidSecurity.verifyData(dataToVerify)
    .then(function(response)
    {
        var message = response == 1 ? "Verified" : "Not Verified"
        $ionicPopup.alert({title:"Verify Data", template:message});
    });
}

logout

platform_icon cordova sample

var app = {

//...

    logOut: function()
    {
        security.logout(function(response)
        {
            alert("Logout success");
        })
    },

//...

platform_icon angular sample

//inside your .controller

 $scope.logout = function() {
    $rapidSecurity.logout().then(function(response) {
        $ionicPopup.alert({title:"Logout", template:"Logout Successful."});
    })
    .catch(function(e)
    {
        alert(e);
    });
}

setDialogProperties

platform_icon cordova sample

var app = {

//...

    setupCustomDialogs: function(customLogo)
    {
        var defaultPropertiesJson = security.defaultDialogProperties();
        var dialogConfig = JSON.parse(defaultPropertiesJson);

       // Set Create Pin Background to blue
       dialogConfig.allProperties[1].properties.RapidBackground.colour = "#0000FF";

       // Set Verify Pin background to green
       dialogConfig.allProperties[2].properties.RapidBackground.colour = "#00FF00";

       // Set Authenticate Pin background to red
       dialogConfig.allProperties[3].properties.RapidBackground.colour = "#FF0000";

       // Set Authenticate Pin Ok Caption to Verify
       dialogConfig.allProperties[3].properties.RapidOkCaption.text = "Verify";

       // Set Authenticate Pin Cancel Caption to Cancel
       dialogConfig.allProperties[3].properties.RapidCancelCaption.text = "Cancel"

       // Set Custom Logo on Authenticate Pin
       dialogConfig.allProperties[3].CustomLogo = customLogo;

       security.setDialogProperties(dialogConfig, function(){});    
    },

//...

platform_icon angular sample

//inside your .controller

$scope.customiseDialogs = function(customLogo)
{
    var defaultPropertiesJson = $rapidSecurity.defaultDialogProperties();
    var dialogConfig = JSON.parse(defaultPropertiesJson);

    // Set Create Pin Background to blue
    dialogConfig.allProperties[1].properties.RapidBackground.colour = "#0000FF";

    // Set Verify Pin background to green
    dialogConfig.allProperties[2].properties.RapidBackground.colour = "#00FF00";

    // Set Authenticate Pin background to red
    dialogConfig.allProperties[3].properties.RapidBackground.colour = "#FF0000";

    // Set Authenticate Pin Ok Caption to Verify
    dialogConfig.allProperties[3].properties.RapidOkCaption.text = "Verify";

    // Set Authenticate Pin Cancel Caption to Cancel
    dialogConfig.allProperties[3].properties.RapidCancelCaption.text = "Cancel"

    // Set Custom Logo on Authenticate Pin
    dialogConfig.allProperties[3].CustomLogo = customLogo;

    $rapidSecurity.setDialogProperties(dialogConfig)
    .then(function(response) {
        $ionicPopup.alert({title:"customisation", template:"Successful."});
    })
    .catch(function(e)
    {
        alert(e);
    });
}

Dialog properties JSON

To configure the dialogs that are displayed by the RapID security library you can call the defaultDialogProperties method to get a json representation of the configuration. This can then be modified appropriately and passed back to the setDialogProperties method which will then apply the customization.

A small snippet of the Json that is retrieved back is shown:

 "allProperties":[
           {
         "DialogType":"RapidAuthenticateFingerprint",
         "CustomLogo":"",
         "properties":{
            "RapidTitle":{
               "title":"Title",
               "text":"RapID Authenticator",
               "colour":"",
               "colourValid":true,
               "textValid":true
            },
            "RapidMessage":{
               "title":"Message",
               "text":"Fingerprint verification",
               "colour":"",
               "colourValid":true,
               "textValid":true
            },
            "RapidHeadline":{
               "title":"Headline",
               "text":"Please touch to authorize",
               "colour":"",
               "colourValid":true,
               "textValid":true
            },
            "RapidCancelCaption":{
               "title":"Cancel caption",
               "text":"Cancel",
               "colour":"",
               "colourValid":true,
               "textValid":true
            },
            "RapidFingerprintSuccessMessage":{
               "title":"Fingerprint success message",
               "text":"Fingerprint recognized",
               "colour":"",
               "colourValid":true,
               "textValid":true
            },
            "RapidFingerprintNotRecognizedMessage":{
               "title":"Fingerprint not recognized message",
               "text":"Fingerprint not recognized. Try again",
               "colour":"",
               "colourValid":true,
               "textValid":true
            },
            "RapidFingerprintLockoutMessage":{
               "title":"Fingerprint lockout message",
               "text":"Too many attempts. Try again later",
               "colour":"",
               "colourValid":true,
               "textValid":true
            },
            "RapidFingerprintHelpMessage":{
               "title":"Fingerprint help message",
               "text":"Keep your finger on the Home key a little longer",
               "colour":"",
               "colourValid":true,
               "textValid":true
            },
            "RapidBackground":{
               "title":"Background color",
               "text":"",
               "colour":"",
               "colourValid":true,
               "textValid":false
            }
         }
      }
 ]}

The allProperties property contains an array of dialogs that can be customized. The example shown is the RapidAuthenticateFingerprint dialog type. It has an array of properties that can be modified.

            "RapidHeadline":{
               "title":"Headline",
               "text":"Please touch to authorize",
               "colour":"",
               "colourValid":true,
               "textValid":true
            }

One such property is the headline of the authenticate fingerprint dialog. The JSON allows you to configure the text that is displayed along with what colour this text is. The properties colourValid and textValid tell you that the values are allowed to be set. In the example of the setupCustomDialogs function you can see that the line below is setting a specific dialogs property - RapidOkCaption, and setting its text to Verify.

dialogConfig.allProperties[3].properties.RapidOkCaption.text = "Verify";

Note: on iOS, the only field that is applicable for the RapidAuthenticateFingerprint dialog type is the headline.

Custom logo image

In the two examples outlined above, both take in a variable of customLogo. This is a base 64 encoded string of the image that will be used for your custom logo.

One way this can be loaded is using the Cordova File Plugin which can be installed and saved to your config.xml file with the below command:

cordova plugin add cordova-plugin-file --save

The plugin enables you to read an image file from your local directory as a base 64 data url. The below code attempts to load an image called custom_logo.png from the apps images directory.

var app = {
    // Application Constructor
    OnDeviceReady: function() {      

        //Read in custom logo image and set up custom logo + dialogs
        window.resolveLocalFileSystemURL(cordova.file.applicationDirectory + "www/images/custom_logo.png", app.fileLoadSuccessCallback, app.fileLoadErrorCallback);
    }
};

You will notice that the resolve local file method also takes in two callback methods:

var app = {

    fileLoadErrorCallback: function(e) {
        alert(e);
    },

    fileLoadSuccessCallback: function(fileEntry)
    {
        fileEntry.file(function(file) {
            var reader = new FileReader();

            reader.onloadend = function(e) {
                var dataUrl = e.target.result;

                // We have to do index of comma as we need to strip the leading
                // part of the data url to get at the base64 encoded file
                // data:image/png;base64,UI89as9....
                var index = dataUrl.indexOf(',');
                var base64 = dataUrl.substring(index+1);
                app.setupCustomDialogs(base64);
            }

            reader.readAsDataURL(file);
        });

    }

 };

On error we simply alert the problem. On success, we read the file as a data url and then need to strip off part of the leading string to get the pure base 64 representation of the image which is passed into the appropriate setupCustomDialogs method.


Two way TLS using sendRequest

Cordova apps will need to call sendRequest explicitly. In cordova the api method is actually called sendRequestEx

platform_icon cordova sample

var app = {

//...

login : function(data) {
    var url = "https://yoursite.com/authenticate"
    var data = { 'Challenge' : data };
    var dataString = JSON.stringify(data);
    var dataLength = dataString.length;
    var headersIn = "Accept: application/json\nContent-Type: application/json\nContent-Length: " + dataLength;
    var redirect = "true";
    var timeout = "120";
    var label = containerLabel
    var forceAuth = "RapidUseCache"; // or pass in "RapidForce" to always enter pin
    var titleFail = "Authentication Failed";

    security.sendRequestEx("post", url, headersIn, data, redirect, timeout, label, forceAuth,
     function(response) {          

        if(response.err)
        {
            var message = security.states().Text(response.rapidstatus);
            alert(message);
            return;
        }

        var message = "";
        if(response.status == 403)
        {
            console.log("403 Unauthorized, check the whitelist is defined in the manifest.");
            message = "Access Denied.";
        }
        else if(response.status == 5001)
        {
            console.log("5001 Please check your network connectivity.");
            message = "Please check your network connectivity and ensure fingerprint or PIN provided for authentication.";

        }
        else if((response.status < 200) || (response.status >= 300))
        {
            console.log("Server response: " + response.status);
            message = "QR Code contents not accepted by the RapID server.";
        }
        else
        {
            message = "Login complete.";
        }

        alert(message);

      }); // end success function and send request ex call

 },

//...


Two way TLS using angular http intercept

HTTP traffic is intercepted automatically by the Rapid security angular wrapper when using the core angular ng http service. The whitelist (configured during Rapid security plugin install) is used to determine whether to call sendRequestEx under the hood.

The Rapid label and Rapid authentication parameters can be provided in the http request using header entries Rapid-AuthCertLabelName and Rapid-Authentication.

platform_icon angular sample using angular core http service (Example from Rapid Demo App)

//inside your .service

.service('AccountBalances', ['$http', '$q', 'settings', '$rapidSecurity', 
function($http, $q, settings, $rapidSecurity) {

    var self = this;

    this.load = function() {
        return $q(function(loaded, cname) {
            try {
                var reportUrl = settings.getSampleAppReportUrl();  
                $http.post(reportUrl,  '', {headers: { 'Rapid-AuthCertLabelName': 'RapidContainer', 'Rapid-Authentication': 'RapidForce'}})
                .then(function(resp) {

                //check if rapid status has been
                var rapidStatus = $rapidSecurity.states().RapidSuccess; 
                try {
                    var statusObj = JSON.parse(resp.statusText);
                    if (statusObj.hasOwnProperty('RapidStatus')) rapidStatus = statusObj.RapidStatus;
                }
                catch (e) {
                    console.log("statusText is not json : " + resp.statusText);
                }

                //http status maybe 200 - but the response could still be an rapid error
                if (rapidStatus != $rapidSecurity.states().RapidSuccess)
                {
                    var errorString = $rapidSecurity.states().Text(rapidStatus);
                    alert("Report post failed : " + errorString);           
                }
                else
                {
                    //do success path here
                }
            }, function(err) {
                console.log("Something went wrong.\n\nPlease contact the service center");
            });
            } catch(e) {
                console.log("Report post failed:" + e.message);
            }
        });
    }

}])

platform_icon angular sample making explict call to Rapid Security sendRequestEx

//inside your .controller

 $scope.startLogin = function(challenge) {

    var request = settings.getChallengeUrl();

    var data = { 'Challenge' : challenge };
    var dataString = JSON.stringify(data);
    var dataLength = dataString.length;
    var headersIn = "Accept: application/json\nContent-Type: application/json\nContent-Length: " + dataLength;
    var redirect = "true";
    var timeout = "120";
    var label = containers.getCurrentContainerLabel();
    var forceAuth = "RapidUseCache"; // or pass in "RapidForce" to always enter pin
    var titleFail = "Authentication Failed";

    $rapidSecurity.sendRequestEx("post", request, headersIn, dataString, redirect, timeout, label, forceAuth)
        .then(function(response) {
              $timeout(function() {

                var rapidStatus = $rapidSecurity.states();
                // This is the error handling information you can get from sendRequest

                switch(response.rapidstatus) {
                    case rapidStatus.RapidClientAuthCertValidInFuture:
                    {
                        console.log("cert valid in future");
                    }
                    break;
                    case rapidStatus.RapidClientAuthCertExpired:
                    {
                        console.log("cert expired");
                    }
                    break;
                    case rapidStatus.RapidServerCollectionNotPermitted:
                    {
                        console.log("collection not permitted");
                    }
                    break;
                    default:
                    {
                        if(response.status == 403)
                        {
                            console.log("403 Unauthorized, check the whitelist is defined"
                        }
                        else if(response.status == 5001) //special case - returned by plugin
                        {
                            console.log("5001 Please check your network connectivity.");
                        }
                        else if((response.status < 200) || (response.status >= 300))
                        {
                            console.log("Server response: " + response.status);
                        }
                        else
                        {
                            console.log("Login fail");
                        } 
                    }
                    break;
                }
            }, 500);
        }).catch(function(err) {
                $timeout(function() {
                    console.log("Login fail: " + "Internal Server Error");
                }, 500);
        });      
    }  
}