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 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 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.
Gallery 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.
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.
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.
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:
tutorials$ git clone https://github.com/approov/hands-on-api-proxy.git
NASA requires a valid registration key to access their free pictures of the day service. Open a browser and visit https://api.nasa.gov/index.html#apply-for-an-api-key. Complete the registration, and save your API key in a safe place.
An attestation service is used to establish trust between client and proxy server. Open a browser and visit https://www.approov.io/demo-reg.html to get access to the free demo service. Complete the registration, open your email, and unpack the zip file into a convenient place. In the sample directory tree shown, I have placed it under the same parent directory as the api-key-proxy repo.
During the tutorial, you will be making changes to the mobile client and the api key proxy. A working directory, pen
has been created for you which holds starting copies of the client app and the API key proxy.
For both client and proxy, each completed step of the tutorial is stored under the steps
directory. You can copy any of these into your working directory if you want to start at some intermediate point.
The config
directory will be used shortly to pre-configure the steps with your specific keys and secrets.
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, preferably version 2.3 or later. 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.
The Node.js environment is used to build and run the example key proxy server. Ensure the Node.js environment is installed, preferably a stable version 6 release. If you need a fresh install, visit Node.js to get started. Install the node version manager (nvm) if you want to maintain multiple node versions.
This will install Node and its package manager, npm. We’ll install additional package dependencies as we build each proxy.
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.
The config
directory contains a Node script which will help configure all the sample code steps. First change into the config
directory, and install the required Node dependencies:
tutorials$ cd api-key-proxy/configtutorials/api-key-proxy/config$ npm install
Next copy the sample secrets config file, secrets.sample.yaml
, into secrets.yaml
, and open it for editing:
tutorials/api-key-proxy/config$ cp secrets.sample.yaml secrets.yaml
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 10.0.2.2
as the address (the emulator’s default tunnel to localhost
), and select an unused localhost port, 8080
in the sample file.
Change the nasa_api_key
and approov_token_secret
values to match the NASA API key you received and the Approov token secret you obtained in the demo download. Point approov_android_lib
to the location of the approov.aar
file inside the demo download. Save secrets.yaml
, and run the configuration script:
tutorials/api-key-proxy/config$ npm start
If the configuration was correct, all client and proxy steps now hold a consistent set of config and secret values, the approov demo service SDK library has been added to the relevant client steps, and the proxy URL has been set for both clients and proxies.
The pen
working directory contains copies of the starting configurations, 0_direct_client
and 1_open_proxy
. You should be good to go.
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 working directory at api-key-proxy/pen/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.
You may see notifications or error messages about not being able to load modules and possibly a dialog box asking if it is okay to remove the modules:
This usually happens if you have moved android projects between directories, for example if you copied one of the completed steps into your working pen
directory. It should be safe to Remove Selected
modules, and you project should sync without further issues.
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:
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:
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:
You should see something like the screenshot on the left:
Good (left) and Bad (right) Gallery Screens
If you see the screen on the right, you likely you have not configured your API key properly or you have a network connectivity issue. Open a browser or Postman, and try to access the NASA service, replacing DEMO_KEY in the screenshot below with your own key:
Debug your setup connectivity and API key until you get a valid response. Ensure that your api_key
is properly set in the secrets.xml
file and also that your api_url
is properly set in config.xml
to https://api.nasa.gov
. Double check that you haven’t set either of these values in another file such as strings.xml
.
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_
.
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. As long as a key is used by the client, the client’s APK contains a static representation of that key. In our example, using the Apkool, 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.
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.
To start working on the proxy server, change into the proxy working directory and install any missing Node dependencies:
tutorials$ cd api-key-proxy/pen/proxytutorials/api-key-proxy/pen/proxy$ npm install
This may take awhile, and you may see some warnings for several dependencies which may not be necessary depending on your host platform.
Depending on your initial configuration, your src/config.js
file should look something like this:
You are moving the NASA API key off the client app and into the proxy, so your src/secrets.js
file should look like this:
Make sure nasa_api_key
is set to your NASA API key string.
Like your client’s secrets.xml
file, the secrets.js
file will be safely ignored by git.
The proxy server is a straightforward 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.
Start the proxy server:
tutorials/api-key-proxy/pen/proxy$ npm start
You should see a message that the server is listening on the port specified for the proxy in config.js
.
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.
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, check that your API key is correct inside secrets.js
and that your server is running, is listening on the proper port, and is able to access the external network.
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. Even though this is a simple Express app, it is setup to automatically load all route handlers found in the src/api
directory. To add additional API key proxies, you would create a new route handler similar to nasa.js
and add the URL and key to the configuration and secrets files.
For reference, a completed version of the proxy server at this stage is in _steps/proxy/node/1_open-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 10.0.2.2
using http:
. So, for a default emulator and local proxy server, the proxy URL would be [http://10.0.2.2:8080/api.nasa.gov](http://10.0.2.2:8080/api.nasa.gov/planetary/apod.)
.
Change the api_url
value appropriately in the app/src/main/res/values/config.xml
file:
You no longer want the API key in the client, so delete the app/src/main/res/values/secrets.xml
file. You also should delete the api_key
from the urlRequest
query string in the PhotoRequester
getPhoto()
function:
With these small changes in config.xml
and PhotoRequester.java
, rebuild the modified client app.
Ensure that the proxy server is 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, double check your proxy URL and network connectivity. If the proxy is local, make sure you are using http:
rather than https:
protocol, and double check the host address and port number. Alternatively, fire up a browser on your client device or emulator, and try to access the proxy server from there. If running on an emulator, this should help verify that 10.0.2.2
is the correct address for localhost.
For reference, a completed version of the client app at this stage is in _steps/client/android/1_open-client_
.
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 attestation service to authenticate the client app as someone you trust.
You will hook into an attestation service by using the Approov demo package you downloaded earlier. Once the attestation service is running with the client (next section), the client will pass a short-lived JSON Web token (JWT) to the proxy server. Using an application secret known only to the attestor and the proxy server, the proxy server can validate the JWT signature and expiration, and if valid, can fulfill the proxy request.
To do this we will add the demo token secret and JWT token checking to the proxy server. From the demo package, open the secret/token-secret.txt
file. Copy the token string and paste it into the src/secrets.js
file as the approov_token_secret
:
The approov token will be passed using a request header named approov
. During debug, we want to verify tokens but allow requests to pass even if the tokens fail verification. Open the src/config.js file and add
approov_header and approov_enforcement
entries:
The original index.js
file contained a pre-process all requests route handler:
This is just a simple placeholder which will now be replaced by a pre-process all route handler which will check for a valid approov token with every request. Replace the placeholder routine with this code:
Restart the proxy server, and restart your Android client app. In the proxy server console, you should see failing token checks, but the server is still fulfilling the NASA API requests because enforcement is disabled. The client app should still be displaying photos as usual.
Next, in src/config.js
, change the approov_enforcement
value to true
. Restart both proxy server and client app. Now you should see failing attestations and unauthorized access errors coming back to the client app.
For reference, a completed version of the proxy server at this stage is in _steps/proxy/node/2_secure-proxy_
.
Next you will add the missing attestation functionality into your client app.
You start by adding the Approov SDK library into the client project. The library is found in the approov demo package at libraries/android/approov.aar
.
Within Android Studio, click the Build -> Edit Libraries and Dependencies…
menu item. In the Project Structure dialog box, ensure the app module is highlighted and the Dependencies tab is selected. Click the **+**
button on the upper left side of the dialog box. In the New Module dialog box, select the Import .JAR/.ARR Package
option and click Next
. In the file name field, navigate to your libraries/android/approov.aar
file and click OK. The Subproject name field will be filled in with the name of the AAR file, approov
in this case. Click on Finish
. A gradle project sync will start.
Back in the Project Structure dialog box, click on the **+**
button on the upper left right of the dialog box and select 3. Module Dependencies
. In the Choose Modules box, select the :approov
module and click OK
.
You use the Approov SDK to create an Approov attestation object. This object will periodically ask the attestation service to verify its authenticity, and it will cache the resulting short-lived validation token. For API calls, an interceptor object is also added which will intercept all API requests and add the most recent token to the request headers.
These few changes are all contained within the App class. Replace the app/src/main/java/com/criticalblue/android/astropiks/App.java
source file with:
No other changes should be required.
Ensure that the proxy server is running. 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, open a terminal or command window and change into the directory inside the unzipped approov demo archive which holds the proper registration tool for Android and your local environment. On Linux, for example, that directory is registration-tools/Android/Linux. Run the registration tool specifying the path to your application APK. If your approov demo is located at ~/tutorial/demo
, your Astropiks project is at ~/tutorial/api-key-proxy/pen/client
, then:
tutorial$ cd demo/registration-tools/Android/Linux
tutorial$ ./registration -a ~/tutorial/api-key-proxy/pen/client/app/build/outputs/apk/app-debug.apk -t ../../registration_access.tok-e 2d
Submitting data…
Success: new app signature added to database.
You can check in the Approov portal that signature vmz6h52XaGJ5VZPWC5W4YQ6kWlU7KR0im/NrVhQclas= has been added to the library 1490625733147480904
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_
.
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.
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. You also add a simple passthrough proxy for the apod
image requests. Replace the existing src/api/nasa.js
file with:
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
.
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.
Thanks for reading! I’d really appreciate it if you recommend this tutorial (by clicking the ❤ button) so other people can find it.
To learn more about API security and related topics, visit approov.io or follow @critblue on twitter.