Approov Integration with Cordova

Thu 11 May 2017 By John

Approov and Cordova

We are always on the lookout for different frameworks for building mobile apps to make sure Approov plays nicely with them, this article covers Cordova and by extension Phonegap and shows how you can integrate Approov with them. However, we are not experts in every framework, so if you can see a flaw or a better way of doing what we are doing, get in touch!

The most common way of integrating into Cordova is by using the plugin's source and integrating that with the final installation package. Approov is a security library with a native component and is provided pre-compiled. As such we need to use a slightly different flow to integrate it with a Cordova app.

Integration

In order to follow this integration tutorial, you can either install the Cordova or PhoneGap CLI. You can find the official installation instructions for Cordova here. Then you will need the Android .aar or iOS framework for Approov that you want to integrate. You can get one of these by signing up for a free trial or by downloading the demo.

The steps involved are:

  • Create Bindings - the glue between the Approov SDK and the Cordova app
    • Define the unified JavaScript interface
    • Setup the Android bindings
    • Setup the iOS (objective-C) bindings
    • Configure the plugin
  • App Integration - use the Approov SDK in your app

1. Create Bindings

In order to use a native library, we will have to create some files that define the bindings we will use to communicate with the native code from within the Cordova javascript app. To do this we create a plugin, our directory structure will end up looking like this:

Approov in Cordova project

1.1 Unified JavaScript interface

The first file we create is the JavaScript interface. There is only one entry function into our library API, so it is very small. It takes in success and failure callback, which are executed according to the result of the function call to the native library.

approov.js

var ApproovPlugin = {
    fetchApproovToken: function(success, error) {
        cordova.exec(success, error, "ApproovPlugin", "fetchApproovToken", []);
    },
}

module.exports = ApproovPlugin;

The ApproovPlugin variable and the fetchApproovToken function correspond to the JavaScript calls you make within your app, while the arguments passed in to cordova.exec are the names associated with your native binding's class and function name. These names do not need to match each other but we are doing that here to make it easier to trace the different functions being called. We also export our ApproovPlugin object for a more seamless app integration.

1.2 Android Binding

This binding consists of an initialize function and an execute function, which are both Cordova related functions.

initialize - Gets called as the plugin is being instantiated. Provides an opportunity to call any set up functions for the plugin.

execute - Matches up the function calls from the JavaScript interface to the implementation in the plugin. This is where the logic for the usage of JavaScript callbacks happens.

Cordova does not allow code to block the main app's JavaScript thread, that would impact the user experience, so we need to use the Cordova threading pool to create a worker runnable object to execute our native call.

ApproovPlugin.java

package com.criticalblue.cordova;

import com.criticalblue.attestationlibrary.ApproovAttestation;
import com.criticalblue.attestationlibrary.android.AndroidPlatformSpecifics;
import com.criticalblue.attestationlibrary.TokenInterface.ApproovResults;
import org.json.JSONArray;
import org.json.JSONException;
import org.apache.cordova.*;

// Approov Java Interface
public class ApproovPlugin extends CordovaPlugin {

    ApproovAttestation attestation;

    // Initialisation
    @Override
    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
        super.initialize(cordova, webView);
        // Approov initialisation
        // Create ApproovAttestation object
        AndroidPlatformSpecifics platformSpecifics = new AndroidPlatformSpecifics(this.cordova.getActivity().getApplicationContext());
        attestation = new ApproovAttestation(platformSpecifics);
    }

    // Cordova binding mechanism to Approov SDK
    @Override
    public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException{
        if("fetchApproovToken".equals(action)) {
            // Execute in another thread to avoid blocking
            cordova.getThreadPool().execute(new Runnable() {
                public void run() {
                    // Fetch token
                    final ApproovResults approovResults = attestation.fetchApproovTokenAndWait();
                    // If Successful
                    if(approovResults.getResult() == ApproovAttestation.AttestationResult.SUCCESS) {
                        // Execute success callback
                        callbackContext.success(approovResults.getToken());
                    } else {
                        // Execute error callback
                        callbackContext.error("");
                    }
                }
            });
            return true;
        }
        return false;  // Returning false results in a "MethodNotFound" error.
    }
}

Now we have the Android binding ready to incorporate and link to our native SDK, we need to create a Gradle file to specify this dependency. We describe the associated directory path, name of library and its file extension type. In here, we also specify the minimum Android SDK version for our library. This configuration will interact with any existing settings for the main app, so we have to take care to not overwrite the existing settings with ones that are less restrictive.

approov.gradle

def DEFAULT_MIN_SDK_VERSION = 11
def minSdk = Math.max(DEFAULT_MIN_SDK_VERSION, cdvHelpers.getConfigPreference('android-minSdkVersion',0) as Integer);

if (cdvMinSdkVersion == null || Integer.parseInt(cdvMinSdkVersion) < minSdk ) {
    ext.cdvMinSdkVersion = minSdk;
}

repositories{
    jcenter()
    flatDir {
        dirs 'lib'
    }
}

dependencies {
    compile(name:'approov', ext:'aar')
}

1.3 iOS Binding

iOS shares similar logic with the Android binding implementation. The pluginInitialize method is available for native library startup logic but we don't need it for our iOS SDK. For the same reason mentioned in the Android binding instructions, it is necessary to use the threading method runInBackground to launch a new background thread to perform any computation for you.

ApproovPlugin.h

#import <Cordova/CDV.h>

@interface ApproovPlugin : CDVPlugin
-(void)fetchApproovToken:(CDVInvokedUrlCommand*)command;
@end

ApproovPlugin.m

#import "ApproovPlugin.h"
#import <Approov/Approov.h>
#import <Foundation/Foundation.h>

@implementation ApproovPlugin
-(void)fetchApproovToken:(CDVInvokedUrlCommand*)command
{
    [self.commandDelegate runInBackground:^{
        CDVPluginResult* pluginResult = nil;
        // fetch Approov token
        ApproovTokenFetchData *tokenResult = [[ApproovAttestee sharedAttestee] fetchApproovTokenAndWait];
        switch (tokenResult.result) {
            case ApproovTokenFetchResultSuccessful:
            {
                // get Approov token
                NSString *approovToken = tokenResult.approovToken;
                // send the Approov token securely to your server for validation, for example, in an HTTPS header
                pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:approovToken];
                break;
            }
            case ApproovTokenFetchResultFailed:
            {
                // handle the failure
                pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@""];
                break;
            }
        }
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
    }];
}
@end

1.4 Plugin Config

Each plugin has a plugin.xml file that pulls everything together. It is a manifest file that contains the platform specific information and all the bindings and files for this Cordova plugin. For our use case, this file can be dissected into three components; the js-module and platform specific config for Android and iOS. The general explanation of this file can be found here, so let's dive into the configuration specific to the pre-built native libraries.

For Android, we need to specify the path to the gradle file as a framework and the path to your native Android Archive (.aar) file as a resource-file. This will in turn copy these files to the Android platform section for your app when you add the plugin and Android as a platform. For iOS, your framework path is specified with framework and because it is a custom library, all dependencies including the core libraries like "Foundation.framework" have to be explicitly declared here. Here is the specific config for the pre-built libraries:

plugin.xml

Android

<source-file src="src/android/com/criticalblue/cordova/ApproovPlugin.java" target-dir="src/com/criticalblue/cordova"/>
<framework src="src/android/approov.gradle" custom="true" type="gradleReference" />
<resource-file src="lib/approov.aar" target="lib/approov.aar" />

iOS

<header-file src="src/ios/ApproovPlugin/ApproovPlugin.h"/>
<source-file src="src/ios/ApproovPlugin/ApproovPlugin.m"/>
<framework src="lib/Approov.framework" custom="true" embed="true" />

<!-- iOS Approov SDK dependency -->
<framework src="JavaScriptCore.framework" />
<framework src="UIKit.framework" />
<framework src="Security.framework" />
<framework src="WebKit.framework" />
<framework src="libobjc.A.dylib" />
<framework src="libSystem.B.dylib" />
<framework src="CoreFoundation.framework" />
<framework src="CoreGraphics.framework" />
<framework src="SystemConfiguration.framework" />
<framework src="Foundation.framework" />

2. App Integration

After having constructed the plugin, now comes the easy part. We have exported our ApproovPlugin object so we don't need to include it as a JavaScript file in the index.html. All there is for us to do is call the function directly in our index.js and use the returned value for your purposes. In Approov, we obtain an authentication token string from a success callback and attach it to an API request.

index.js

function getToken() {
    // We pass in a callback that will be called with a token from our server
    // when this function completes
    ApproovPlugin.fetchApproovToken(makeServerRequest, error);
}

// Make server request with Approov Token
function makeServerRequest(tokenString) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.setRequestHeader("Approov-Token", tokenString)
    // Normal flow for sending your request
    ...
}

There is a little more work to do for iOS. As of Cordova iOS 4.3.1, there is no mechanism to automatically add frameworks as embedded binaries, without using 3rd party code. See Cordova "Embedded Binaries" issue. There is a pull request for this feature already merged so it should be available soon. For now, here is the manual procedure for adding the "Approov.framework" to the set of "Embedded Binaries":

  • Open the iOS project with xcode
  • In the Xcode project editor, select the target you want to add the Approov framework to
  • Select the "General" tab
  • Select the + (plus) icon under the "Embedded Binaries" section
  • Select "Approov.framework" from the file dialog
  • Select "Add"

Summary

Now we have shown how simple it is to create a Cordova plugin integrated with the Approov pre-built native library. In the future we plan to make it even easier by automating the process and providing a download with our library pre-configured. In the meantime, this guide allows Cordova developers to easily integrate Approov with their Android and iOS platforms. Check it out and give us feedback if anything is unclear.

Category: Integration