Server Integration

The Approov service uses JSON Web Tokens to represent the authenticity of client apps. This in an open and standard mechanism for representing claims in a tamper proof form and your web service will need to decode and verify these tokens as part of the Approov flow. This guide extends a very simple web service, built in the Python language and using the Flask framework, to demonstrate the integration procedure. The examples use the pyjwt library for working with JWT tokens, however, libraries for working with JWT are available in most common languages, with many listed on the JWT homepage. The approach described here will be applicable when using other technologies.

Example Server

Below is a very simple hello world server, written in python.

Python
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
   return "Hello World!\n"

if __name__ == "__main__":
   app.run()

As we progress through this integration example we will extend the above server to accept and verify Approov tokens which are used to show that an incoming request originated from an attested app. Our example assumes that the token is in the Approov-Token header of an incoming (secured) http request. However, communication of the JWT is not limited to the approach described here, or to a specific protocol. Using the appropriate calls to the Approov SDK API allows you to retrieve the attestation token and communicate it to the server in whatever way is most appropriate for your use-case.

ApproovToken Header

In our example the Approov tokens are added in a framework independent way to the request header. The Approov-Token header contains the JWT token representing the app authenticicity:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZCI6Ilczc2lhMlY1SWpvaVJFbEVJaXdpZG1Gc2RXVWlPaUpxUW1wd1FXcExZa0o2ZW1sVFUzSjNXV3BrZG1ScFFtZzBhV0pNYm5OdFFtTlVWbTgzTTFsd1JFbzBQU0o5TEhzaWEyVjVJam9pUVZJaUxDSjJZV3gxWlNJNkltZ2lmVjA9IiwiZXhwIjoyNDcxNDE5OTI2LCJpcCI6IkFBQUFBQUFBQUFBQUFQLy9WRnhDS0E9PSIsInVpIjo3MjU1MDI5MjIzNDczNjY5MDAwfQ.ieDCvsWqF7DyDd7ShII1X1xK392NkIuO6m2oBpic8zg

One way to communicate Approov tokens in a framework independent manner is to embed them in an HTTP request header. The following example demonstrates the procedure for accessing the requester’s Approov-Token HTTP header when using Flask and then isolates the Approov token. In this example, the token validity is checked and if the token is valid a random Shape is returned.

Python
def hello():
    '''
        Simple Hello World end point
    '''
    return 'Hello World!'

Verifying The Token

JWT are made up of three sections: a header, a payload and a signature. To create the signature, a URL safe base64 encoding of the header and payload sections is produced and used as the input to a HMAC, using a chosen hash algorithm and a given secret. Approov uses the SHA-256 algorithm for hashing. The secret used to verify the token must match the secret which was used to sign it (the Token Secret). It is generated for you when the Approov service is initialized and it can be copied to your server code from your Approov Admin Portal, see The Approov Admin Portal.

The simplest way of verifying the Approov token is simply to attempt to decode it using the Token Secret. To do this with the pyjwt library we use the decode() function. The decode() function takes the base64 decoded token, the secret, and the algorithm used to create the signature. If the token is valid the payload data is returned. If the App used to generate the token has not been registered or a bogus token is used then this check will fail. This function also checks the expiry claim made within the token and, if the token has expired, the function returns a specific error value. The expiry check performed by the pyjwt library is typical of libraries that process JWT, however, to be certain, you should ensure that the same check is made by the JWT library you choose for your server. Additionally, as token expiry is time based, we recommend that all servers using Approov tokens should synchronize with a public NTP server.

The following example demonstrates how to decode an Approov token to decide whether to service this request. Recall that for a production build of your server you must copy the Token Secret from your admin portal.

Python
def basic_verify_token(token):
    '''
        Verify token without checking the IP claim and return the tokens
        contents.  This is the simplest type of token check.

        Args:
            token string: An Approov Token

        Returns:
            token_contents dict: The contents of the token
    '''
    try:
        token_contents = jwt.decode(token, base64.b64decode(SECRET), algorithms=['HS256'])
        return token_contents
    except jwt.ExpiredSignatureError:
        # Signature has expired, token is bad
        return None
    except:
        # Token could not be decoded, token is bad
        return None

Advanced Token Use

IP Address Claim

Approov tokens contain the IP address of the client to which the token was issued. This property can be used for monitoring or to increase the security of your service by rate limiting tokens which have an IP mismatch.

When using this feature, consideration must be given to your client’s behavior. There are various factors that may cause a mismatch between the IP encoded in the Approov token and the IP from which the request originates:

A user’s IP address may change at any time during a session, i.e. when shifting from WiFi to mobile data. The Approov client library reacts to events that notify listeners of IP address changes and this causes a new attestation to obtain a token containing the new address. However, there is a race-condition in the event that an IP address changes after a web service request has been constructed, but before or during its sending. In this case, the token IP claim will not match.

On certain mobile network configurations it is possible that an IP address can change between an attestation request and an API call.

If you are running your server on an IPv4-only network and the client was on an IPv6 network, the values will not match. Approov will see the client’s IPv6 address and store it in the token. Your server, however, will see another IPv4 address which the client has been routed through.

When running in an Android emulator with a local server, there may be a mismatch in the IP address seen by the server and the ip address seen by the Approov servers.

Note that if you feed attestation results to traffic analysis software then it will need to take account of this type of behaviors.

Warning

Because of these scenarios, we recommend that the IP address is only used for rate limiting requests and not for absolute blocking.

The IP address sent in the token is the IPv4-mapped IPv6 address of the device the token was issued to. This is then converted to binary and represented as base64. The following example demonstrates how to get this into a human readable form.

Python
def get_comparable_ip(token_ip):
    '''
        Convert the IP address given in an Approov Token into a human readable
        IP address

        Args:
            token_ip string: IP in format given in Approov Token

        Returns:
            comparable_ip ipaddress.IPv6Address: IP in standard format
    '''
    ip_bin = base64.b64decode(token_ip)

    comparable_ip = ipaddress.IPv6Address(socket.inet_ntop(socket.AF_INET6, ip_bin))

    return comparable_ip

Note

When an IPv4 address is encoded by our SDK it uses the standard method for holding it inside an IPv6 address. That is to use the form: “0 0 0 0 0 0 0 0 0 0 255 255 W X Y Z” for the ip address W.X.Y.Z

The following example demonstrates how to get the IP address from the token and check whether it matches with the IP address of the origin of this request.

Python
def advanced_verify_token_ip(token, client_ip):
    '''
        Verify token and check the IP claim matches with the IP which used
        the token.  If ok return the tokens contents.

        Args:
            token string: An Approov Token
            client_ip ipaddress.IPv6Address: IPv6 address of the client

        Returns:
            boolean: True if token is ok
    '''
    token_contents = basic_verify_token(token)
    if token_contents is None:
        return False

    # Get IP from token contents if present then check it
    try:
        issued_ip = get_comparable_ip(token_contents['ip'])

        # Check if IP we see has been mapped from IPv4 to IPv6
        if client_ip.ipv4_mapped and issued_ip.ipv4_mapped is None:
            '''
                If IP from token is native v6 and we are on v4 we can't
                compare, we won't see the same address as Approov
            '''
            return True

        if issued_ip == client_ip:
            return True
    except KeyError:
        '''
            There is no IP claim, we don't need to check it.
            In certain circumstances no IP claim will be available,
            this is entirely valid
        '''
        return True

    # Token was not decoded successfully, token is bad
    return False

Multi-layer Network Architecture

The simple code example above assumes that the requests received by the web service are unmodified from the point they are sent.

In practice, it is common for requests to pass through intermediate services such as a proxy, content delivery network, or load balancer before reaching your web service. In the cases of interest, the intermediate service is the termination point for the secure channel before forwarding the request to your web service. To ensure that the eventual destination of the request can obtain the original IP from which the request comes the intermediate service typically injects a new X-Forwarded-For address into the request. In your web service implementation you must take account of the network infrastructure that is in place, and the changes it will make to requests, and build that into your verification logic.

Care must be taken not to open up attack vectors, for example: one attack may be to attempt using a valid token from an invalid IP address by setting the X-Forwarded-For property in the header of a message, before it is sent. This attack cannot succeed if your token validation logic interprets message contents based solely on the request transformations applied by your own network infrastructure. I.E. in the above case, the load balancer would append the IP of the request origin to the list of entries in the X-Forwarded-For field. Your web service, configured to handle the precise configuration under which it sits, examines only the last entry of the X-Forwarded-For field to determine if the message origin and token IP addresses match. Falsified X-Forwarded-For field entries are not a threat to your system in this case.

The point here is that the web service implementation needs to correctly decode requests with respect to the Network Architecture within which it sits. Having a single place in your service that contains the code to decode messages for token verification will significantly simplify the development and maintenance of your Approov integration.

Customer Payload Data

The Approov service can optionally include a hash of some data provided to the SDK inside the Approov Token issued to the app. This binds that data to the Approov Token and thus proves it was present in the app when its integrity was verified. It is assumed that the bound data is transmitted to your API via some existing mechanism and therefore a check can then be made of the corresponding Approov token to ensure that it was indeed bound to that data, as an additional security protection. For example, a representation of an OAuth token could be included in an Approov token to ensure that the token can only be used by the user it was issued to. The data is hashed using the SHA256 algorithm, encoded as base64 then inserted into the Approov token in the “pay” claim.

The recommended process for using the data is as follows: 1. Do a basic check of the Approov Token to see if it is valid. 2. Check whether the token has a “pay” claim. If pay claim is not present then the token is either from the Approov Failover service, from an older Approov SDK or from an app which is not using the payload feature. 3. If the claim is present then the data contained should be compared with the representation of the data you provided to Approov. In some cases, such as when you are providing an OAuth token and you have yet to retrieve this in application code, you should use a known “none” value which you both pass into the token fetch call in your app and in your server side payload check.

To make use of this feature you must also make some changes to your usage of the Approov SDK see Including a custom claim and Including a custom claim.

The following example shows the expected data being past into a function. The source of this data will depend on your application, it may have been extracted from a header sent from your application or it may be the known none value. If the Approov Token is valid this is then hashed, base64 encoded then compared with the value extracted from the Approov Token.

Python
def advanced_verify_token_payload(token, expected_payload):
    '''
        Verify token and check if payload data matches with the payload from a
        custom header.  Return token validity.

        Args:
            token string:            An Approov Token
            expected_payload string: Data which has been sent to Approov to match
                                     with the 'pay' claim in the Approov Token

        Returns:
            boolean: True if token is ok
    '''
    token_contents = basic_verify_token(token)
    if token_contents is None:
        return False

    if 'pay' in token_contents:
        # Compare payload from token with expected_payload
        encoded_header = base64.b64encode(hashlib.sha256(expected_payload).hexdigest())
        if token_contents['pay'] != encoded_header:
            return False

    return True

Monitoring Service Health

The Approov Cloud Service incorporates an always-on Health Monitoring System which actively monitors the state of various internal cloud server components, reporting the overall health state and operational readiness.

The current health state can be queried through a publicly-available REST endpoint:

https://healthcheck.approovr.io/healthcheck/<customer-name>

Note

The <customer-name> in the above example should be replaced with the customer name string given to you by CriticalBlue.

This endpoint will return a “200 OK” HTTP response with a JSON body including a “HealthState” object with value “initializing”, “passed” or “failed”:

  • The initializing health state appears temporarily after a restart of service components and indicates that the system is gathering data to determine the health status. When this operation is complete, the health monitor will report a “passed” or “failed” health state as appropriate.
  • The passed health state indicates that the system is up and running correctly. All operations can be used as normal.
  • The failed health state indicates that the system has encountered a fault in one or more of its components. The Admin Portal may be down which means the service cannot be administered. Alternatively, the App Authentication service may be down which means client apps may not receive Approov tokens. CriticalBlue monitors the Approov service regularly and adopts a range of automated failover strategies. In the unlikely event that the service is unhealthy, the health monitoring system will report a “passed” state as soon as the system becomes healthy again.