Hands on Mobile API Security - Using a Proxy to Protect API Keys

Image from API(UGC 12591: The Fastest Rotating Galaxy Known. Image Credit:NASA,ESA, Hubble)

API keys and other secrets poorly hidden inside mobile apps are a common source of mobile insecurity. You can do much better.

In this tutorial, you will work with a simple photo client which uses an API key to access the NASA picture of the day service. An API Proxy introduced between your client and the picture service will remove the need for storing and protecting the API key on the client. In addition to improved API security, this approach offers some benefits in manageability and scalability.

During the tutorial, you will modify an Android client and Node.js proxy server. For demonstration purposes, both an Android client emulation and the node server can be run together on a single laptop.

I assume that you have some very basic familiarity with Android and can read Java and Javascript. All code is provided, so it should be possible to follow along even if you have limited experience in these environments.

The Astropiks Mobile App

The Astropiks mobile app is a relatively simple networked Android client with two main screens. The initial screen displays a gallery of recent NASA picture of the day images. Clicking on an image brings up a detailed screen containing the full image and its description.

API Client ApplicationGallery and Detail Screens

The app uses NASA's picture of the day API to retrieve images and descriptions. To access the service, the API requires a registered API key which will be initially stored in the client app.

Why Add an API Proxy?

Apps of any complexity will make extensive use of multiple 3rd party services via their public APIs which means handling and safeguarding many secret API keys. Secrets are supposed to be kept secret. Unfortunately, for a secret held on a mobile app, it's not a question of if it will be stolen but when it will be stolen and with how much effort.

By introducing an API key proxy server between the client app and its 3rd party services, we can remove the API keys from an insecure mobile client and place them on a more secure proxy server. We also add an attestation service to establish trust between client and the new proxy server. This approach is discussed in detail in the Mobile API Security articles.

We'll use the NASA service as an example of an API which would be typically called from a mobile client app. We've added an extra hop between client and service, but it does offer significant benefits. Since we've removed the secret from the client, it's no longer there to be stolen.

If the secret is somehow compromised, it can be replaced at the proxy server with a fresh secret. From a manageability standpoint, since the secret is no longer on the client, the decision can be made to improve security without requiring any change to the installed base of clients.

The proxy service itself is stateless which brings load balancing, failure recovery, and other scalability benefits. By establishing trust between client and proxy server, the attestation service offers a quick rejection filter to drop invalid traffic before burdening the actual API servers. These benefits increase as multiple API services are proxied through the same server.

Preliminary Setup

To get started, you need to download the tutorial source code, get some keys, and ensure your development tools are in place. The tutorial should run properly on windows, mac, or linux environments.

1. Download API Proxy Tutorial Source Code

All tutorial source code is available on github. In a terminal or command window, change to a directory where you will store the tutorial, and clone this public git repository:

git clone https://github.com/approov/hands-on-api-proxy.git

2. Register for an API Key from NASA (It's Free)

NASA requires a valid API key to access their free pictures of the day service. Open a browser and visit this page to apply for one. Complete the registration, and save your API key in a safe place.

3. Setup Android Studio

Android Studio and the Android SDK are used to build and run the Astropiks client app. Ensure Android Studio is installed and reasonably up to date, like version 3.6, the one used during this tutorial . If you need a fresh install of the Android tools, go to the Android Developers Site to get started.

The tutorial presumes you will be running the client in an Android emulator, but you can also use an Android phone or tablet. The Android device should be running API 19 or higher.

4. Proxy Server Setup

The proxy server runs on Node.js, but  for this demo we will use some online API backends for each step in this demo, so that we can have an hassle free experience.

Why Node.js and Android?

Android and Node.js environments were chosen as sample demonstration environments. Other implementations, including iOS for the client and NGINX or Go for the proxy, are certainly appropriate. If you would like to see other client or proxy implementations, add a suggestion in the responses, and I will see what can be added.

5. Configure the Code Samples

Android Studio

Set proxy_home to be a fully qualified URL address and protocol. If you will be running the Android client in an emulator and the proxy server locally, use http: as the protocol and as the address (the emulator's default tunnel to localhost), and select an unused localhost port, 8080 in the sample file.

Build the Initial Client App

The initial client app communicates directly with the NASA picture of the day service. The NASA API key must be stored in the app and provided with each API call.

Fire up Android Studio and open the Astropiks Android client in the step 0 directory at `./steps/client/android/0_direct-client`. Android Studio will take a while to load library dependencies, and rebuild the project. If your configuration is valid, the project should build without errors.

This application is a fairly simple model-view-controller style master-detail photo gallery. The PhotoRequester class handles the initial API calls using the OkHttp library, and images are downloaded using the Picasso library. The NASA API URL is stored in the `app/src/main/res/values/config.xml file` and should look like this:

<string name="api_url">https://api.nasa.gov</string>

The App application class creates and provides the HTTP client and downloaders in a convenient place for all activities.

Before running your application, ensure that your NASA API key is properly set in the app/src/main/res/values/secrets.xml file:

<?xml version="1.0" encoding="utf-8"?>
<string name="api_key">YOUR_NASA_API_KEY_STRING_HERE</string>

Create and/or update both these files as necessary.

Notice that secrets.xml has been added to the .gitignore file, so it will not be inadvertently added to your git repository. There are already enough keys checked into GitHub without adding yours!

Now build, install and run the application APK. In Android Studio, hit the Run 'App' button and fire up an emulator or other connected device:

Andorid API Client app build

You should see something like the screenshot on the left:

Andoid API Client

Good (left) and Bad (right) Gallery Screens

If you see the screen on the right, you likely have not configured your API key properly or you have a network connectivity issue.
You can use this troubleshouting guide to help you in debugging your setup for the API key and Nasa url.

Once running, scroll the gallery screen down to fetch more pictures from NASA. Click on a picture to see its detail screen. Hit the back arrow to return to the gallery screen.

For reference, a completed version of the client app at this stage is in steps/client/android/0_direct-client.

Stealing a Client's API Keys

Your API key is used in API calls between client and the NASA server. Most production apps will be calling multiple 3rd party APIs and will be holding multiple API keys.

Client applications should always use TLS best practices, including certificate pinning, to secure the API communications so that your API keys cannot be easily observed, like in a Man in the Middle(MitM) attack. As long as a key is used by the client, the client's APK contains a static representation of that key. In our example, it's a simple matter to reverse engineer your APK and uncover the `api_key` value from the APK. Obfuscation techniques can make this more difficult, but your keys will eventually be found. You can read the article How to Extract an Api Key from a Mobile App by Static Binary Analysis to learn in more detail how it can be done.

Once your API key is discovered, it can be used to access NASA's public services. In this case, repeated calling of the NASA site by a hacker will trigger rate limits, and you will eventually be denied access as a result. But what if you are an e-commerce site? If your site is busy processing nonsense API calls, real customers will be denied access or give up because of poor responsiveness. A malicious hacker might be able to create a bunch of fake orders for merchandise or gain access to competitive information. Alternatively, a hacker could create a look-alike app using your API to display your inventory at a reduced site, gather orders from soon to be angry customers, and grab their credentials and credit card info in the process.

Let's get that secret out of the your app.

Build an Initial API Key Proxy

The proxy server is a straightforward Node.js Express application. The NASA API calls are handled in the `src/api/nasa.js` module. When the proxy server sees a call to route `/api.nasa.gov`, it adds your API key to the query string, proxies the request to the NASA picture of the day service, and forwards the response to the original caller.

The ENV file

The configuration is mainly driven by the `.env` file in the root of the project.

Like your client's `secrets.xml` file, the `.env` file will be safely ignored by git.

You are moving the NASA API key off the client app and into the proxy, so you will add it now to the `.env` file:


Next we want to configure the Astropik API domain:


Followed by the port it will be listening on:

Start the Node.js Proxy Server
To start the proxy server with the Docker stack just launch it from the root of your project with:
./astropik up 1_open-proxy

In the first run it will install the Node.js dependencies, and this may take a while, and you may see some warnings for several dependencies which are not necessary for the platform the Docker container is running on.

You should see a message that the server is listening on the port you specified earlier in the `.env` file for `ASTROPIK_HTTP_PORT`.

Testing the Node.js Proxy Server

Try out your proxy using a browser or postman to call the proxy. Assuming you are running locally on port 8080, replace a direct call to NASA with the now proxied call htttp://localhost:8080/api.nasa.gov/planetary/apod?date=2017–01–01&api_key=YOUR_API_KEY. Notice we are also switching protocols from https to http. This is unsafe for production, but makes it easier to run our example locally without pinning the connection.

Postman API Client

The JSON response includes the date, an explanation, and one or more URLs to download MEDIA. If you see a well formed response, your proxy server is working well. Otherwise, you can use this troubleshouting guide to help you in debugging your setup.

Though this example only uses one API and therefore only requires one API key, most applications will use multiple APIs with multiple API keys. An API key server must be able to proxy multiple different API proxies. To add additional API key proxies, you would create a new route handler similar to `nasa.js`, require it in `index.js`, add the URL and API key to the env file, and ensure that is loaded by the configuration file.

For reference, a completed version of the proxy server at this stage is in steps/proxy/node/1_open-proxy.

Modify the Client App to Use the API Key Proxy

Now that the API key proxy is working, you can modify the Android client to go through the proxy. You will use the same port number you used above. By convention, if running in an Android emulator, localhost can be accessed through address using http. So, for a default emulator and local proxy server, the proxy URL would be

For your convenience you can now open in your Android Studio the mobile app for the step 1, that it's located at `steps/client/android/1_open-client`, or if your prefer just keep modifying the current one.

So, from step 0 we change the `api_url` from having the url to the NASA API to have the proxy url on localhost, as we can see at  `./steps/client/android/1_open-client/app/src/main/res/values/config.xml file` :

<string name="api_url"></string>

You no longer want the API key in the client, so the secrets file is not present anymore in step 1, as you can see at `steps/client/android/1_open-client/app/src/main/res/values`.

You also should delete the `api_key` from the urlRequest query string in the `getPhoto()` function for the class `PhotoRequester`.

So we go from this code in step 0:

String urlRequest = mContext.getString(R.string.api_url) + BASE_PATH + DATE_PARAMETER + date;

urlRequest += API_KEY_PARAMETER + mContext.getString(R.string.api_key);

final Request request = new Request.Builder().url(urlRequest).build();

To this code in step 1:

String urlRequest = mContext.getString(R.string.api_url) + BASE_PATH + DATE_PARAMETER + date;

final Request request = new Request.Builder().url(urlRequest).build();

With these small changes in `config.xml` and `PhotoRequester.java`, rebuild the modified client app.

Ensure that the proxy server is still running, and install and run the modified client app on the Android emulator or device as before. You should see the same result as before, but now the photos are being served through the API key proxy. You should see photo requests in the API key proxy console log. If you do not see photos, you can use this troubleshouting guide to help you in debugging your setup.

For reference, a completed version of the client app at this stage is in steps/client/android/1_open-client.

Add Proxy Security

Congratulations, you should now have a functioning proxy server. There is no longer any secret on the client, but you have a proxy which can be used by anyone. You need a way to ensure that only trusted clients can access your proxy server without requiring any new secrets on that client. One way to do that is to use a dynamic mobile app attestation service to establish trust between client and proxy server, and you can find a SaaS solution at Approov, that is easy to integrate, both in your mobile app and API server.

Approov Integration

The Approov integration in the mobile app consists in adding the Approov SDK, registering the mobile app with the Approov cloud service, and integrating a check for the Approov token in the API server. Optionally you can also tailor the configuration defaults used by the Approov cloud service to better match your needs, for example by changing the security policy that is used to determine when the Approov cloud service can issue a valid Approov token for the mobile app.

Approov CLI Tool

During the Approov implementation we will need to use the Approov CLI tool which can be downloaded and installed by following these instructions in the Approov docs, where you can skip the part of signing for an Approov account, and instead request a demo download here, and when you receive the email just copy the token on it and past it into a file in your computer. This token is necessary to use the Approov CLI tool with the Approov Cloud Service. As recommended in the instructions, do not forget to set the developer token in your shell, or instead you will need to give the path to it in each Approov command. The attachment can be ignored, once it’s for running a Shapes demo.

Approov SDK

You will hook into the Approov mobile app attestation service by using the Approov SDK that you will download with the Appoov CLI. Once ist's integrated in your mobile app, and the APK registered with Approov (next section), the client will receive from the mobile app attestation service a short-lived JSON Web token (JWT) to pass down to the proxy server, that was signed using a secret known only to the Approov mobile app attestation service and to the proxy server. Once the proxy server receives a request it must contain a JWT, that is then checked for having a valid signature and expiration time, and if valid it can fulfill the proxy request, otherwise it can refuse it.

To make all this more straight forward to implement we have created an Approov Framework to wrap the Approov SDK, that is specific for each http stack, and you can also find it in `framework` folder of the quick start guide. For example the framework adds an interceptor to the http stack, which will intercept all API requests and add the most recent Approov token to the request headers. 

Integrating Approov in the Proxy Server

The approov token will be passed to the proxy server in a request header named `Approov-Token`, checked with an Approov base64 encode secret, and you can enable or disable to abort the request on an invalid Approov token. 

Open the `.env` file and add this entries:


NOTE: For peace of mind, in an the first release we may want to verify tokens but allow requests to pass even if the tokens fail verification, and for this we jsut need to keep `APPROOV_ABORT_REQUEST_ON_INVALID_TOKEN` set to `false`, and just change it to `true` when we fill confortable in doing so.

In order to trigger the Approov token check we will use the middleware approach by calling the `approov-token-check.js` from the `index.js` file:

app.use('/*', approov.checkApproovToken)

app.use('/*', approov.handlesApproovTokenError)

app.use('/*', approov.handlesApproovTokenSuccess)

We have also a new file `approov-config.js`, that is responsible for loading the `.env` file variables for Approov, that will be required to perform the Approov token check.

Stop the proxy server for step 1:
./astropik down 1_open-proxy
Start the proxy server for step 2:
./astropik up 2_secure-proxy

Now restart your Android mobile app and In the proxy server console you should see failing token checks, but the server is still fulfilling the NASA API requests because aborting the request on an invalid token is disabled. The mobile app should still be displaying photos as usual.

Now let's enable the request to abort on invalid Approov tokens by editing the `.env` file like this:


 Restart the proxy server for step 2:

./astropik restart 2_secure-proxy

Now restart again the mobile app. Now you should see failing attestations and unauthorized access errors coming back to the mobile app.

Android API Client

For reference, a completed version of the proxy server at this stage is in steps/proxy/node/2_secure-proxy.

Integrating the Approov SDK in the Mobile App

In order to add the Approov mobile app attestation functionality, the Approov SDK library must be added into the mobile app project, and we can download it with the Approov CLI tool:

approov sdk -getLibrary ./steps/client/android/2_secure-client/approov/approov.aar 

The step 2 it's already prepared to load the Approov SDK, but when installing it in your own mobile app you will need to do some more steps, and for that you should follow the Approov quick start for your http stack, that in the case of this app would be this one.

So the few changes necessary to add Approov are all contained within the App class:

 --- /home/sublime/workspace/steps/client/android/1_open-client/app/src/main/java/com/criticalblue/android/astropiks/App.java
+++ /home/sublime/workspace/steps/client/android/2_secure-client/app/src/main/java/com/criticalblue/android/astropiks/App.java
@@ -23,6 +23,8 @@

import okhttp3.OkHttpClient;

+import io.approov.framework.okhttp.ApproovService;
* Represents the astropix application.
@@ -33,12 +35,15 @@
OkHttpClient mClient = null;
Picasso mDownloader = null;

+ public static ApproovService approovService;
public void onCreate (){

- mClient = new OkHttpClient.Builder()
- .build();
+ approovService = new ApproovService(getApplicationContext(), getResources().getString(R.string.approov_config));
+ mClient = approovService.getOkHttpClient();
mDownloader = new Picasso.Builder(this)
.downloader(new OkHttp3Downloader(mClient))

Isn't this easy? We added the Approov mobile app attestation with only 4 lines of code. No other changes should be required.

If you have been running the proxy in your own computer then it's time to update the url for the proxy server in order to use the online proxy provided by us, or in laternative you need to deploy it in an online server. This is necessary because Approov comes with dynamic certificate pinning(more on this later) built in that requires a real domain with a trusted ssl/tls certificate for the proxy server, thus it must be reachable from the internet.

<string name="api_url">https://astropik.approov.io/v2/api.nasa.gov</string>

Now rebuild and run the Android client app. Sadly, the Astropiks client is still not showing photos!

If you have done everything correctly, the Approov service is now attesting the app, but it is still returning a failed validation token. Until you register the app with the attestation service, the attestor has no idea how to validate your app.

To register the app use the Approov CLI tool from the root of this repo with:

approov registration ~/Developer/development.tok -add steps/client/android/2_secure-client/app/build/outputs/apk/debug/app-debug.apk -expireAfter 2d

Note that you are using and registering the debug APK, app-debug.apk. If you are working with a production version, make sure your APK is properly signed before you register it with the Approov service.

The app registration above is set to expire in 2 days. No matter how long the expiry is set for, the Approov demo service will periodically remove client registrations, so if you start up your client again in the future, you may need to register it again with the demo service.

Give the Approov demo service a few seconds, and your app should be properly registered. Restart your client app, and if everything is correct, the attestor service will continuously validate your client, the proxy server will verify the Approov tokens and fulfill the API requests, and the Astropiks client will once again show a gallery of NASA pictures of the day!

For reference, a completed version of the client app at this stage is in steps/client/android/2_secure-client.

No Fake Clients

The secret is no longer on the app. The proxy server holds the API key, and the attestor service and the proxy server share a secret to establish trust between client and proxy server. Is it possible to build a fake app without the API key?

In Android Studio, try building a fake app by simply cleaning and rebuilding the existing app. Reinstall and run the freshly built app. Does it run successfully?

Even though it has exactly the same functionality, this is not the same APK build so the attestation fails. If you want to use this new app, you must separately register this new version.

How Mobile App Attestation Works

Approov Serverless Reverse Proxy in the AWS API Gateway-2

Now that you have seen the Approov mobile app attestation in action you may want to have a better undertanding in how all wokrs.

Phase 1

As we can see in the above firgure on phase 1, the Approov SDK manages the integraty checks with the Approov cloud service. Each time the SDK contacts the APproov cloud it gets a set of challenges for taking measurements that must be returned to the Approov cloud, were the decison is made for determining if the app have passed or not all challenges. On a suscessefull attestation a JWT token is issued and signed with a secret only know by Approov and the mobile app API, and on failure a secret not known by the mobile app is used to sign the JWT, therefore both the valid and invalid JWT look the same to anyone instrospecting them, meaning that even the mobile app is not able to distinguish them.

Phase 2

The mobile only concern is to add the received JWT token in each header of a request to the API, and use https hover a pinned connection to the API, but this burden is taken away from you by wrapping the use of the Approov SDK with the Approov Framework specific for your http stack. Pinned connection means that certificate pinning must be implemented, and you may already be screaming and having nightmares due to past experiences, but once more we have you covered and we take care of this for your through the use of dynamic certificate pinning, that uses the Approov dynamic configuration capibilities to keep the pins up to date in the mobile app, therefore not locking out your users due to human error in mamaging pinning or because you need to rotate them due to whatever reason. In the next section we will go in more detail about Dynamic Certificate Pinning.

Phase 3

Here is where you smile and attackers get sad for not being able to pass through, because the API will only accept requests when the JWT has not expired and was signed with the secret only known by the API and the Approov cloud.

When we say it's simple to verify it in the API, we mean it. See how it's done in this demo:

// Callback that performs the Approov token check using the express-jwt library
const checkApproovToken = jwt({
secret: Buffer.from(config.approov.base64Secret, 'base64'), // decodes the Approov secret
requestProperty: 'approovTokenDecoded',
getToken: function fromApproovTokenHeader(req, res) {
req.approovTokenError = false
const approovToken = req.get('Approov-Token')

if (isEmptyString(approovToken)) {
req.approovTokenError = true
throw new Error('Approov Token empty or missing in the header of the request.')

return approovToken
algorithms: ['HS256']

Offcourse then you need to decide what to do when it fails and when it succeeds, and all you need is to register your callbacks:

app.use('/*', approov.checkApproovToken)

app.use('/*', approov.handlesApproovTokenError)

app.use('/*', approov.handlesApproovTokenSuccess)


Dynamic Certificate pinning


With Approov you get it for free, and without any of the short commings of traditional certificate pinnings implementations, where it's easy to lock out your customers from using your mobile app, by relaese the mobile app with the wrong pin set or when the certificates are rotated in the API server.

Why do we need certificate pinning?

We need certificate pinning to mitigate MitM attacks that may want to manipulate request and reponses between your mobile app and API server, to steal sensnitive data, or even to steal the Approov token.

How is dynamic certificate pinning implemented?

The Approov SDK takes care of tracking the TLS connection used by the app to perform authenticated server API requests. Essentially, if the connection to the Approov Cloud Service or any protected API endpoints is proxied, the Approov authentication process can detect that and provide an invalid Approov Token, therefore letting your backend know that the request cannot be trusted.

When you released the mobile app you where asked to provide with the Approov CLI tool what domain it was using, and this his necessary for the Approov cloud service to extract the pins for the TLS certificate used by your domain, and provide them to your mobile app via a dynamic configuration. It is worth noting here that Approov achieves this dynamic TLS certificate pinning without you having to embed any certificate data in the app, and worry about updating it when a certificate expires or the certificate's private key is compromised. When this happens or you just need to rotate the pins, it's simple as using the Approov CLI tool to set the new pins in the Appoov cloud service, and in the next time the Approov SDK fetche an Approov token it will receive an Over the Air(OTA) update for the Approov configuration, that will trigger the OkHttp stack to be rebuilded with new set of pins, thus without the need to release a new version of the app and wait that all your customers update it, therefore without impacting your customers user experience.

If you are curious about the code, you can take a look into the Approov Framework here:

// build the pinning configuration
var pinBuilder = CertificatePinner.Builder()
val pins = Approov.getPins("public-key-sha256")

for (entry in pins.entries) {
for (pin in entry.value)
pinBuilder = pinBuilder.add(entry.key, "sha256/$pin")

As you can see we get the pins from the Approov SDK, that under the hood retireves them from the Approov dynamic configuration., and then we build the CertificatePinner object with the received pins, that afterwards are used to build the OkHttp stack, as seen here:

okHttpClient = okHttpBuilder!!.certificatePinner(pinBuilder.build())

Now you can finally use certificate pinning in your mobile app without fearing for the worst, and have nightmares with certificate pins rotation.

Enhancing the Proxy Service

This is an optional section which modifies API responses so that image download requests also run through the API key proxy and are therefore verified using approov tokens. This is an example of how to add additional processing within a proxy route handler to fix up URIs contained in responses, to adapt or combine API calls or to add additional security.

Look again at the JSON response from the earlier browser calls. The response includes URLs used to download images. Notice that these URLs go directly to NASA and do not require an API key.

For additional security, we could run these through our API proxy server as well. You do this by rewriting the image download URLs in the response JSON to go through the proxy. Javascript regular-expression replacement rewrites the URL strings, and we also need to fix up the content-length header to account for the longer JSON body:

proxyBody = proxyBody.replace(apodDirectRe, apodProxy);
proxyRes.headers["content-length"] = proxyBody.length.toString();

You also add a simple passthrough proxy for the apod image requests. Replace the existing src/api/nasa.js file with:

  app.get('/' + apodHostname + '/apod/image/*', (req, res, next) => {
console.log('Processing NASA apod image request', api_protocol + '/' + req.url);

// pipe the image request through the proxy
let proxyUrl = api_protocol + '/' + req.url;
let proxyReq = request(proxyUrl);


Restart the modified proxy server, and restart your Android client app. The client app should still be displaying photos as usual. In the proxy server console, you should now see both NASA API and NASA apod image requests.

When proxying more complex APIs, you will need to be careful to transform responses to fix up proxied resource URIs and other redirects.

For reference, a completed version of the proxy server at this stage is in steps/proxy/node/3_enhanced-proxy.

In the Wild

Don't forget to use TLS with certificate pinning in production so that the communication channel is properly secure.

Each version of your application will be registered separately, You can register and unregister versions as you wish, possibly forcing an update of unregistered apps at next use.

When secrets were held inside apps, replacing a compromised key required an update to the existing installed base of clients. Now, since the API keys are no longer on the client, they can be easily changed on the API key proxy server without requiring any change to your installed base of mobile clients.

Despite the extra hop, API key proxy servers offer enhanced security, scalability, and recovery benefits.


Try Approov For Free!