We're Hiring!

How We Integrated Approov With Cordova

Mezquita mosque in Cordova, Spain


Editor's note: This post was originally published in June 2018 and has been revamped and updated for accuracy and comprehensiveness. The latest update was in March 2021.

Outline

Overview

In this blog we use the integration of Approov with Cordova as an example for how to integrate Approov with a mobile app development platform while keeping changes to the platform and the apps that are built on top of it to a minimum. This is a quite detailed exposition on how to build an Approov plugin to use with Cordova Advanced HTTP. The integration techniques used here are also relevant for how one would use Approov on other mobile app development platforms. If you would just like to know how to use the Approov plugin, head straight to Using Approov in a Cordova App or try the Approov Cordova quickstart, but if you are curious about the details of how the integration works, read on.

Cordova is a platform for building native mobile applications using HTML, CSS and JavaScript. It is an open source project managed by the Apache Software Foundation.

Approov improves mobile security by enabling dynamic software attestation for mobile apps. It allows mobile apps to uniquely authenticate themselves as the genuine, untampered software you originally published. Upon successfully passing the integrity check the app is issued a short-lifetime token which can then be presented to your API with each request. This allows your server side implementation to differentiate between requests from known apps, which will contain a valid token, and requests from other sources, which will not. This gives you complete control over what you allow to talk to your mobile API server.

Cordova Advanced HTTP (also on NPM) is a popular Cordova plugin for communicating with HTTP(S) servers and works for both Android and iOS. Throughout this blog we are using Cordova Advanced HTTP as the example for Approov integration.

Approov in a Nutshell

Before we look at the actual integration with a platform, we need to know a little more about how Approov works and how you use it with a native app. Approov is provided as a native SDK (Android .aar, iOS .framework and .xcframework) that you integrate into your Android and iOS apps (see also the Approov SDK User Guides for Android and iOS).

The Approov SDK provides a simple API for authenticating a mobile app and requesting an Approov token from the Approov Cloud Service to represent the app’s authenticity. The app can then transmit the token to your mobile API servers for validation:

The SDK initiates the token fetch to which the Approov Service replies with a challenge to the SDK to prove its own and the app's authenticity. The SDK responds to the challenge by performing an integrity check of itself and an authenticity check of the app, the result of which is verified remotely by the Approov Cloud Service. The Approov Cloud Service then provides the SDK with a unique, cryptographically-signed, time-limited Approov token whose validity is only known by the Approov Cloud Service and the API servers which hold the “token secret”.

While the Approov token representing the app’s authenticity is securely delivered to the SDK, and by extension the app, it is the mobile app's responsibility to ensure the token is securely transmitted to the mobile API server. The connection between app and API must use HTTPS and be pinned to the API server to prevent theft of valid Approov tokens through a man-in-the-middle (MitM) attack, because a stolen, valid Approov token can be used to impersonate a bona-fide app.

This can be achieved either by traditional static certificate pinning or through Approov's dynamic pinning capability.

Static pinning techniques specify the certificate or its public key that you are expecting from the API server and ensure that secure connections are only made to that server, thus blocking MITM attempts.

Static pinning can be difficult to manage operationally due to dependencies between app and server. The app needs to know the certificate to pin and this is usually embedded in the app. This means that a certificate update on the API server requires that the app must also be updated, which can be mitigated to some extent by having a second, backup certificate embedded in the app.

Dynamic Pinning

Ideally what is required is a means to transmit the updated pins over-the-air immediately to the app without any need for an app update. This needs to be done in a secure manner to prevent an attacker using this as a back door to inject their own pins to undermine the pinning protection.

Approov holds the set of public key pins for API domains being protected, inside an SDK configuration file. This initial configuration file is distributed as part of the app. The SDK configuration is signed using Elliptic Curve Cryptography (ECC), with the public key held in the initial SDK configuration and the private key held securely in Approov’s servers. If any change is made to the pins, then an updated dynamic configuration will be transmitted to any app that requests a new Approov token. The dynamic configuration is signed with the ECC private key, preventing any possibility of tampering and proving that the update has been issued by the Approov servers. This updated dynamic configuration will be written to the local storage of the app, and will either have an impact next time the app is started or immediately, depending upon the implementation of the pinning in the app. This verified updated configuration overrides the settings from the initial SDK configuration.

For a more detailed explanation and an example please refer to the Approov MITM Detection documentation.

Approov SDK

The native Approov SDK provides a lean interface that consists of just a handful of functions.

Initialization
  • initialize(application context, initial configuration, dynamic configuration, comment)

    Before using any features of Approov you will first need to initialize the Approov SDK. Initialization should be performed at application start-up so the Approov SDK is available to perform attestations straight away. On Android, Approov initialization requires the application context. The initial configuration must be non-null but the dynamic configuration may be null. The comment parameter is reserved for future use and should be set to null. If there is a problem with the initialization then an exception will be thrown and it will not be possible to execute further methods in the SDK.

Approov Token Functionality

  • ApproovTokenFetchResult fetchApproovTokenAndWait(domain)
    fetchApproovToken(ApproovTokenFetchCallback, domain)
    Synchronously or asynchronously perform an attestation of the running app and fetch an Approov token from the Approov Service. The synchronous version blocks the caller until completion (or timeout) and returns the result of the token fetch operation. The asynchronous version returns immediately and on completion (or time out) invokes the callback with the result of the token fetch operation.
    The method takes a parameter of the particular API domain for which a token is being fetched. This must be one that has been configured to provide Approov tokens or else an error will result. Usage:
    • Immediately before making an HTTPS request to your server/cloud/backend API, make a fetchApproovToken call to retrieve a token from the Approov Service.
    • Add the Approov token to the header of the request and send the request securely, using HTTPS with certificate pinning, to the API server.
    • Your server can then check the token's validity and, if the token is found to be invalid, which indicates that the request did not come from a genuine app or was transmitted through an insecure connection, reject the request.

  • setDataHashInToken(data)Includes a hash of arbitrary data as one of the Approov token’s claims. This is intended for long lived data, such as an OAuth token or a user session id, that can be used to uniquely identify a user. This token binding can be used to extend the security of the Approov token through a strength in depth approach. 

Dynamic Public Key Pinning Functionality

  • getPins()

    Returns the currently configured set of pins to be used, keyed by domain. The pins will be set up immediately after the initialization of the Approov SDK. Usage:

    • After making the fetchApproovToken(domain) call and adding the token to the request to the API server, but before actually sending the request, call getPins().
    • Use the returned pins for the domain to pin the connection to prevent the request from being sent through an insecure connection.
    • If the request fails because of the pinning, go back and retry the request from the step where fetchApproovToken(domain) is called, or take a different appropriate action.

Cordova Advanced HTTP

The Cordova Advanced HTTP plugin provides functions for communicating with HTTP(S) servers for both Android and iOS. Its Android implementation is based on Http Request a convenience library for using an HttpURLConnection to make requests and retrieve the response. Its iOS implementation uses AFNetworking a networking library built on top of Apple's Foundation framework's URL Loading System.

Cordova Advanced HTTP supports the corresponding HTTP(S) requests through the JavaScript functions post, get, put, patch, delete and head, and also provides functionality for uploading and downloading files through its uploadFile and downloadFilefunctions. All these functions take an URL, some data or query parameters, headers and success and error callbacks as their arguments and on completion, dependent on outcome, call the success or error function with the response as its argument.

Example: The function post(url, data, headers, success, failure) takes five arguments and calls the appropriate response function:

  • url: URL
  • data: payload to be sent to the server
  • headers: headers object (key value pair), will be merged with global values
  • success: success function called on success with a response object that has three properties: status, data and headers:
    • status: HTTP response code as numeric value
    • data: response from the server as a string
    • headers: object containing the headers where the keys are the header names (in lowercase) and the values are the respective header values
  • failure: error function called on failure with a response object that has three properties: status, error and headers:
    • status: HTTP response code as numeric value
    • error: error response from the server as a string
    • headers: object containing the headers

Adding Approov Mobile API Protection to a Platform

We want to provide a ready solution for using Approov with Cordova and are basing this on the Cordova Advanced HTTP plugin. But we also want to give a more general guide on how to integrate Approov into Cordova apps that use a different plugin for their HTTP(S) communication and provide a blueprint for how to integrate Approov with app development platforms in general.

Our overriding goals are that the details of using Approov should stay hidden as much as possible and that the effort for anyone to use Approov in their Cordova apps, be they existing or future ones, be kept to a minimum. We ended up with these:

  • Thou shalt not change the interface of any existing Cordova plugin.
  • Thou shalt not mix Approov-specific code with existing plugin code, or indeed app code.
  • Thou shalt not require more than one change to an existing Cordova app.

This leads directly to our integration approach:

Integration Approach

We are making no changes to the JavaScript interface of the existing HTTP(S) plugin that we are basing our integration on - that means there are no changes in the way existing apps use the HTTP(S) plugin, examples continue to work, etc. But we are extending the plugin's implementation by adding general-purpose interceptor hooks to the existing HTTP(S) plugin so additional functionality can be called if required. As long as these hooks are not used, the plugin's functionality and behaviour are not affected. Our aim is for these hooks to be general and useful enough that they will be accepted as contributions by the maintainers of the existing HTTP(S) plugin. Failing that, since we are only adding small amounts of code to the plugin, it should be easy to keep our version up to date with respect to the original.

In Cordova Advanced HTTP we add a hook just before the call that sends an HTTP(S) request. This hook can be used by anyone who wants to apply "last-minute" changes to a request.

We use these hooks to trigger the execution of interceptors that implement the necessary Approov functionality. Because the calls to the native Approov SDK are performed by the interceptors, an existing mobile app that uses the Cordova Advanced HTTP plugin does not require any code change with regards to its use of the plugin.

We provide the interceptor implementation as a separate Cordova plugin called Cordova Approov HTTP (using the naming style of Cordova Advanced HTTP). The plugin implements all necessary Approov functionality in native code, not JavaScript, effectively hiding the details of the Approov attestation scheme from the app. This is for ease of use and security, but also because access to request headers, connections, certificates, etc is usually not available at the JavaScript level.

The Cordova Approov Plugin provides all necessary functionality for using the Approov scheme (fetching an Approov token, adding it to a HTTPS request, setting up dynamic SSL pinning before each request, if so configured, handling a failure to establish a TLS connection) and performs all the necessary calls to the native Approov SDK.

The only change required to an app is the import of the Approov plugin. This makes for a pretty flat learning curve for anyone who is already using the Cordova Advanced HTTP plugin.

One final thing to be aware of is that requests that always succeeded before Approov protection with dynamic certificate pinning was enabled, may now fail because of a MITM attack or a genuine certificate update - which is of course the purpose of dynamic pinning. To recover from this when the attack ends or to start using a new genuine certificate, such requests must be re-tried - something many mobile apps will do for failed requests anyway, but if an app does not, this would be a further necessary change.

Looking forward, the approach outlined here is the blueprint for Approov integration with other platforms and plugins: Implement the hooks and re-use the Approov-specific code shown here.

On to the actual implementation:

Cordova Advanced HTTP - Adding Interceptor Hooks

Here is one we made earlier: the complete source code of Cordova Advanced HTTP with interceptor hooks is on GitHub.

Android

On Android we add a hook just before the request operation which then allows us to install a custom hostname verifier that performs the certificate check and, if there is a pinning related problem (such as an MITM attack), prevents the request from going out and to clear the Approov SDK's certificate cache.

Cordova Advanced HTTP uses kevinsawicki@gmail.com's HttpRequest

In file cordova-plugin-advanced-http/src/android/com/synconset/cordovahttp/CordovaHttpBase.java:

  1. Define an interface to be implemented by an interceptor. The interface's accept() method takes an HTTP request (that is ready to be sent) as its argument and can perform an action based on the contents of the request or modify the request.

    // Interface type for request interceptors
    public interface IHttpRequestInterceptor {
        public void accept(HttpRequest request);
    }
  2. Add the shared (between all instances of CordovaHttp) list of request interceptors

    // List of request interceptors
    private static Deque<IHttpRequestInterceptor> requestInterceptors = new LinkedList<IHttpRequestInterceptor>();
  3. Provide functions to add interceptors to the list and to apply all interceptors to an HTTP request. Interceptors can only be added to the front of the list to ensure that interceptors added later cannot prevent earlier added interceptors from running or cannot modify earlier changes made to the request.

    // Add a request interceptor to the list of request interceptors
    public static synchronized void addRequestInterceptor(IHttpRequestInterceptor requestInterceptor) {
        if (requestInterceptor == null) {
            throw new NullPointerException("Request interceptor must not be null");
        }
        CordovaHttp.requestInterceptors.addFirst(requestInterceptor);
    }

    Interceptors are applied in reverse insertion order, i.e. most recently added interceptor first.

    // Apply all request interceptors
    public static synchronized void applyRequestInterceptors(HttpRequest request) {
        for (IHttpRequestInterceptor requestInterceptor : requestInterceptors) {
            requestInterceptor.accept(request);
        }
    }
  4. Call the interceptors just before sending the request. In function prepareRequest() add a call to applyRequestInterceptors() as the last action of the function.

    protected void prepareRequest(HttpRequest request) throws HttpRequestException, JSONException {
        this.setupRedirect(request);
        this.setupSecurity(request);
        request.readTimeout(this.getRequestTimeout());
        request.acceptCharset(ACCEPTED_CHARSETS);
        request.headers(this.getHeadersMap());
        request.uncompress(true);
        // Call interceptors to allow "last-minute" changes before performing the request
        this.applyRequestInterceptors(request);
    }

iOS

For iOS things are a little different because there one does not create a request directly, but creates a task (NSURLSessionDataTask) using a manager object that knows how to generate the request and callback functions for handling success or failure of the request operation. This means that we need to put our "request" hook just before the invocation of the manager's request generating function (GET, POST, etc).

Cordova Advanced HTTP uses the Alamo Fire Objective-C implementation.

In file cordova-plugin-advanced-http/src/ios/CordovaHttpPlugin.m:

  1. Define the type for the request interceptor. The request interceptor takes an AFHTTPSessionManager and an URL (NSString) as its arguments and can perform actions using the manager (such as adding a security policy) or the manager's response serializer (e.g. adding a request header).

    // Type for request interceptor
    typedef void (^RequestInterceptor)(AFHTTPSessionManager *manager, NSString *urlString);
  2. Add the shared (between all instances of CordovaHttpPlugin) lists of request interceptors.

    // Lists of request interceptors
    static NSMutableArray<RequestInterceptor>* requestInterceptors = nil;
  3. Provide functions to add interceptors to the lists and to apply all interceptors to an AFHTTPSessionManager or NSError, respectively. Interceptors can only be added to the front of a list to ensure that interceptors added later cannot prevent earlier added interceptors from running or cannot interfere with changes made to the request by earlier added interceptors.

    // Add a request interceptor to the list of request interceptors
    + (void)addRequestInterceptor:(RequestInterceptor)requestInterceptor {
      if (requestInterceptor != nil) {
          @synchronized(requestInterceptors) {
              [requestInterceptors insertObject:requestInterceptor atIndex:0];
          }
      }
    }
    // Apply all request interceptors
    - (void)applyRequestInterceptorsToManager:(AFHTTPSessionManager*)manager URL:(NSString*)urlString {
      @synchronized(requestInterceptors) {
          for (RequestInterceptor requestInterceptor in requestInterceptors) {
              requestInterceptor(manager, urlString);
          }
      }
    }
  4. Ensure the interceptors are called just before sending the request. In function executeRequestWithData:withMethod: and all other functions that initiate requests, add a call to applyRequestInterceptorsToManager:URL:() as the first action of the function.
    Wrap the call to manager POST and call the interceptors before the request:

    - (void)executeRequestWithData:(CDVInvokedUrlCommand*)command withMethod:(NSString*)method {
      AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
      …
      // Run in background as the request interceptors that are called below may be blocking
      [self.commandDelegate runInBackground:^{
          @try {
              // Call interceptors to allow "last-minute" changes before performing the request
              [self applyRequestInterceptorsToManager:manager URL:url];
              …
              if ([serializerName isEqualToString:@"multipart"]) {
              [manager uploadTaskWithHTTPMethod:method URLString:url parameters:nil constructingBodyWithBlock:constructBody progress:nil success:onSuccess failure:onFailure];
              } else {
                  [manager uploadTaskWithHTTPMethod:method URLString:url parameters:data progress:nil success:onSuccess failure:onFailure];
              }
          }
          @catch (NSException *exception) {
              [[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
              [self handleException:exception withCommand:command];
          }
      }];
    }

Cordova Approov HTTP - Using Interceptors to Tie Approov Into Cordova Advanced HTTP

The complete source code of Cordova Approov HTTP is available on GitHub.
 

Cordova Approov HTTP Interface

The interface of Cordova Approov HTTP consists of just two functions that are related to token binding.

In file cordova-plugin-approov-http/www/approov-http.js:

approovSetDataHashInToken: function (data, success, failure)

provides access to the Approov SDK function of the same name.

approovSetBindingHeader: function (header, success, failure)

allows to specify a particular HTTP header that holds the source data for the call to approovSetDataHashInToken. For every request sent, the data is automatically extracted from the specified header.

Cordova Approov HTTP Implementation

As we have the necessary hooks for Approov to be called from Cordova Advanced HTTP, we can have a look at what code we need to execute for each Approov protected HTTPS request.

Recap: In order to use Approov with Approov token theft protection, we need to implement several bits:

  1. Retrieve an Approov token from the Approov Cloud Service
  2. Add the token to the request - as a request header
  3. Ensure that the connection is pinned, using the pins managed by Approov

Android

On Android we are using a custom hostname verifier for the pinning check. This hostname verifier is called during the process of establishing a secure (TLS) connection to check that the certificate hash of the host to which the connection is attempted, matches the desired pin and, if not (failure: SSL connection failed), causes the TLS handshake to be aborted.

CordovaApproovHttpPlugin

CordovaApproovHttpPlugin performs Approov initialization, token fetching, adding the token to a request header, setting up certificate pinning, and certificate checking. We are only showing the parts of the code that are directly relevant to understanding this functionality.

In file cordova-plugin-approov-http/src/android/com/criticalblue/cordova/approov/http/CordovaApproovHttpPlugin.java:

setupApproovCertPinning() creates a custom hostname verifier that performs checking of the remote host’s certificate hash against the pins managed by the Approov SDK. Then it sets this certificate pinner on the connection to ensure that it is called when creating the connection.

// Set up Approov certificate pinning
public static void setupApproovCertPinning(HttpRequest request) throws HttpRequestException {
    // Set the hostname verifier on the connection (must be HTTPS)
    final HttpURLConnection connection = request.getConnection();
    if (!(connection instanceof HttpsURLConnection))
    {
        IOException e = new IOException("Approov protected connection must be HTTPS");
        throw new HttpRequestException(e);
    }
    final HttpsURLConnection httpsConnection = ((HttpsURLConnection) connection);

    HostnameVerifier currentVerifier = httpsConnection.getHostnameVerifier();
    if (currentVerifier instanceof CordovaApproovHttpPinningVerifier)
    {
        IOException e = new IOException("There can only be one Approov certificate pinner for a connection");
        throw new HttpRequestException(e);
    }
    // Create a hostname verifier that uses Approov's dynamic pinning approach and set it on the connection
    CordovaApproovHttpPinningVerifier verifier = new CordovaApproovHttpPinningVerifier(currentVerifier);
    httpsConnection.setHostnameVerifier(verifier);
}

The interceptor is using the hook in Cordova Advanced HTTP. The hook is called immediately before the HTTPS request is sent. The interceptor first handles any binding header if set, fetches a token and, if successful, adds the received token to the header of the HTTPS request. Otherwise it handles any failure cases. Finally it sets up pinning for the request.

// Consumer (operates via side-effects) that sets up Approov protection for a request
public static CordovaHttpPlugin.IHttpRequestInterceptor approovProtect =
    new CordovaHttpPlugin.IHttpRequestInterceptor() {
        @Override
        public void accept(HttpRequest request) {
            // update the data hash based on any token binding header
            if (bindingHeader != null) {
                String headerValue = request.getConnection().getRequestProperty(bindingHeader);
                if (headerValue == null)
                    throw new RuntimeException("Approov missing token binding header: " + bindingHeader);
                Approov.setDataHashInToken(headerValue);
            }

            // request an Approov token for the domain
            URL url = request.url();
            String host = url.getHost();
            Approov.TokenFetchResult approovResults = Approov.fetchApproovTokenAndWait(host);

            // provide information about the obtained token or error (note "approov token -check" can
            // be used to check the validity of the token and if you use token annotations they
            // will appear here to determine why a request is being rejected)
            Log.i(TAG, "Approov Token for " + host + ": " + approovResults.getLoggableToken());
            // update any dynamic configuration
            if (approovResults.isConfigChanged()) {
                // Save the updated Approov configuration
                saveApproovConfigUpdate();
            }

            // check the status of the Approov token fetch
            switch (approovResults.getStatus()) {
            case SUCCESS:
                // Token was successfully received - add Approov header containing the token to the request
                request.header(APPROOV_HEADER, APPROOV_TOKEN_PREFIX + approovResults.getToken());
                break;
            case UNKNOWN_URL:
                // provided URL is not one that is configured for Approov
                break;
            case UNPROTECTED_URL:
                // provided URL does not need an Approov token
                break;
            case NO_APPROOV_SERVICE:
                // no token could be obtained, perhaps because Approov services are down
                break;
            default:
                // A fail here means that the SDK could not get an Approov token. Throw an  the
                // exception containingstate error
                throw new RuntimeException("Approov token fetch failed: " + approovResults.getStatus().toString());
            }
            // ensure the connection is pinned
            setupApproovCertPinning(request);
        }
    };

CordovaApproovPinningVerifier

CordovaApproovPinningVerifier is the custom hostname verifier that apart from performing the hostname check, also checks the certificate pinning.

In file cordova-plugin-approov-http/src/android/com/criticalblue/cordova/approov/http/CordovaApproovHttpPinningVerifier.java:

public final class CordovaApproovHttpPinningVerifier implements HostnameVerifier {
    /** The HostnameVerifier you would normally be using. */

    private final HostnameVerifier delegate;

    /** Tag for log messages */
    private static final String TAG = "CordovaApproovHttpPinningVerifier";

    /**
     * Construct a CordovaApproovHttpPinningVerifier which delegates the initial verify to a user
     * defined HostnameVerifier before applying public key pinning on top.
     *
     * @param delegate  the HostnameVerifier to apply before the custom pinning
     */
    public CordovaApproovHttpPinningVerifier(HostnameVerifier delegate) {
        this.delegate = delegate;
    }

The verify() method is called during establishing an SSL connection. Our custom hostname verifier overrides this method, but before performing its pinning check, calls the original verify() method of the superclass. It then calls our certificate pinner with the leaf certificate obtained from the SSL session.

@Override
public boolean verify(String hostname, SSLSession session) {
   // check the delegate function first and only proceed if it passes
   if (delegate == null || delegate.verify(hostname, session)) try {
       // extract the set of valid pins for the hostname
       Set<String> hostPins = new HashSet<>();
       Map<String, List<String>> pins = Approov.getPins("public-key-sha256");
       for (Map.Entry<String, List<String>> entry: pins.entrySet()) {
           if (entry.getKey().equals(hostname)) {
              for (String pin: entry.getValue())
                   hostPins.add(pin);
           }
       }
       // if there are no pins then we accept any certificate / public key
       if (hostPins.isEmpty())
           return true;
       // check to see if any of the pins are in the certificate chain
       for (Certificate cert: session.getPeerCertificates()) {
           if (cert instanceof X509Certificate) {
               X509Certificate x509Cert = (X509Certificate)cert;
               ByteString digest = ByteString.of(x509Cert.getPublicKey().getEncoded()).sha256();
               String hash = digest.base64();
               if (hostPins.contains(hash))
                   return true;
           }
           else
               Log.e(TAG, "Certificate not X.509");
       }
       // the connection is rejected
       return false;
   } catch (SSLException e) {
       throw new RuntimeException(e);
   }
   return false;
}

iOS

The full iOS source code of Cordova Approov HTTP is available on GitHub.

As for Android, we are only showing the bits that deal with token fetching, adding token to request, setting up pinning and checking certificates. 

In file cordova-plugin-approov-http/src/ios/CordovaApproovHttpPlugin.m, method pluginInitialize:

The request interceptor uses the request hook in Cordova Advanced HTTP which is called immediately before the manager's HTTPS request method is invoked. The interceptor first handles any binding header if set. Then fetches a token and, if successful, adds the received token to the header of the HTTPS request. Otherwise it handles any failure cases. Finally it sets up pinning for the request.
approovProtect = ^(AFHTTPSessionManager *manager, NSString *urlString) {
  // update the data hash based on any token binding header
   if (![bindingHeader isEqualToString:@""]) {
       @synchronized(bindingHeader) {
           NSString *headerValue = [manager.requestSerializer valueForHTTPHeaderField: bindingHeader];
           if (headerValue == nil) {
               NSException* approovMissingBindingHeaderException =
                  [NSException exceptionWithName:@"ApproovMissingBindingHeaderException"
                       reason:@"Request is missing the Approov binding header"
                       userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"%@%@",
                           @"Request is missing the Approov binding header: ", bindingHeader]}];
               @throw approovMissingBindingHeaderException;
           }
           [Approov setDataHashInToken:headerValue];
       }
   }
   // Fetch the Approov token
   ApproovTokenFetchResult *approovResult = [Approov fetchApproovTokenAndWait:urlString];
   // provide information about the obtained token or error (note "approov token -check" can
   // be used to check the validity of the token and if you use token annotations they
   // will appear here to determine why a request is being rejected)
   NSURL *url = [NSURL URLWithString:urlString];
   NSLog(@"%@: Approov Token for %@: %@", TAG, [url host], [approovResult loggableToken]);
   // update any dynamic configuration
   if ([approovResult isConfigChanged]) {
       // Save the updated Approov configuration
       [CordovaApproovHttpPlugin saveApproovConfigUpdate];
   }
   NSString *approovToken = [approovResult token];
   ApproovTokenFetchStatus approovStatus = [approovResult status];
   switch (approovStatus) {
   case ApproovTokenFetchStatusSuccess:
   {
      // Token was successfully received
       // Add Approov header containing the token
       [manager.requestSerializer setValue:[NSString stringWithFormat:@"%@%@", APPROOV_TOKEN_PREFIX, approovToken]
           forHTTPHeaderField:APPROOV_HEADER];
       break;
   }
   case ApproovTokenFetchStatusUnknownURL:
        // Provided URL is a for a domain that has not been set up in the Approov Service
       break;
   case ApproovTokenFetchStatusUnprotectedURL:
       // Provided URL does not need an Approov token
       break;
   case ApproovTokenFetchStatusNoApproovService:
       // No token could be obtained, perhaps because Approov services are down
       break;
   default:
   {
       // A fail here means that the SDK could not get an Approov token. Throw an exception containing the
       // state error
       NSException* approovTokenFetchFailedException =
           [NSException exceptionWithName:@"ApproovTokenFetchFailedException"
               reason:@"Approov could not fetch an Approov token. The unprotected HTTP request must not proceed"
               userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"%@%@%@",
                   @"Approov could not fetch an Approov token. Status: ",
                   [Approov stringFromApproovTokenFetchStatus:approovStatus],
                   @". The unprotected HTTP request must not proceed"]}];
       @throw approovTokenFetchFailedException;
       // Alternatively invalidate the session
       // [manager invalidateSessionCancelingTasks:YES];
       break;
   }}

    [CordovaApproovHttpPlugin setupApproovPublicKeyPinning:manager];

};

Using Approov in a Cordova App

Phew, that is a lot of stuff. But it's all for the purpose of making the integration into an actual app simple. It will also make the integration of Approov or some other service with an app development platform easier next time, as we can re-use the ideas and the code that we have written.

Now that the integration of Approov mobile API protection with the Cordova platform is complete and available for download, what is left is to do the easy bit. We can now use Cordova Advanced HTTP can in the same way as before, but with Approov protection in place - enabling the API server to reject requests that do not originate from a bona-fide app.

Example call to Cordova Advanced HTTP's get request function (note: no changes from what we would normally do):

cordova.plugin.http.get("https://my.domain.com/endpoint", {}, {},
   function(response) {
       // Success
       if (response.status == 200) {
           console.log("Successfully performed GET request");
       }
   },
   function(response) {
       if (response.status != 200) {
           // Failure
           console.log("Error on GET request: " + response.status);
       }
    });

Building and Running an App

When building the mobile app it is important to ensure that the modified Cordova Advanced HTTP with the interceptor hooks is picked up by Cordova, not the original Cordova Advanced HTTP plugin. You also need to include the Cordova Approov HTTP plugin from GitHub in your Cordova project. Then build the mobile app as normal.

You can now run the mobile app, but it will not authenticate until you have registered it with the Approov Cloud Service.

  • An Approov subscription is required for this - it's free for a month, you can sign up here.
  • You also need the Approov command line tool which you can download from the Approov website.

Once the mobile app is registered and after a short propagation delay of no more than 30s, the mobile app will be recognized as valid by our service and will be issued tokens that your mobile API server can check for validity in order to reject bogus traffic.

Note: Attaching a debugger or using a rooted device will be detected by the Approov SDK and you will not get a valid token.

Where To Go From Here

  • The Cordova Approov HTTP Quickstart shows how to use the Cordova Approov HTTP plugin to add Approov Mobile API Protection to requests made through Cordova Advanced HTTP. If you want to try this, please refer to the instructions in the quickstart's README.md. It gives you the chance to see a working system consisting of an app that uses Cordova Advanced HTTP and Cordova Approov HTTP, the Approov Cloud Service and an example mobile API server without having to sign up for a trial subscription.
  • The backend API integration quickstarts guide you through the server-side code required to receive and validate tokens.
  • Do an integration of your own app using Approov's free trial.
  • Create another platform integration of Approov
  • For further information please refer to the full Approov documentation.

That's it, thanks for listening.

 

Try Approov For Free!

Johannes Schneiders