Hands On Mobile API Security: Get Rid of Client Secrets

Written by skiph | Published 2017/05/04
Tech Story Tags: javascript | nodejs | mobile-app-development | security | android | latest-tech-stories | mobile-api-security | get-rid-of-client-secrets

TLDR The Astropiks mobile 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. We’ve added an extra hop between client and service, but it does offer significant benefits. The proxy service itself is stateless which brings load balancing, failure recovery, and other scalability benefits. We also add an attestation service to establish trust between the client and proxy server.via the TL;DR App

Introduce an API Key Proxy to Improve Mobile Security

Pictured Above - UGC 12591: The Fastest Rotating Galaxy Known [Image Credit: NASA, ESAHubble]
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

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.

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:
tutorials$ 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 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.
3. Download Attestation Demo Service Package (it’s free)
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.
4. Setup Android Studio and SDK
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.
5. Setup Node.js
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.
6. Configure the Code Samples
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/config
tutorials/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
# API Key Proxy Secrets Configuration

# specify a fully qualified proxy home url, for example, http://10.0.2.2:8080
proxy_home:           'http://10.0.2.2:8080'

# specify api key string received from NASA
nasa_api_key:         'NASA_API_KEY_VALUE'

# specify approov token secret as a base64 string extracted from approov demo 
approov_token_secret: 'APPROOV_TOKEN_SECRET_VALUE'

# specifiy location of approov android library (approov.aar) extracted from approov demo
approov_android_lib:  '../../demo/libraries/android/approov.aar'
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.

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 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:

<resources>
    <string name="api_url">https://api.nasa.gov</string>
</resources>
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:
<resources>
    <string name="api_key">YOUR_NASA_API_KEY_STRING_HERE</string>
</resources>
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.

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. 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.

Build an Initial API Key Proxy

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/proxy
tutorials/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:

module.exports = {
    proxy_port:             /* port the proxy listens on */
        8080,
    nasa_host:              /* NASA API host */
        'api.nasa.gov',
    nasa_protocol:          /* NASA API protocol */
        'https:',
};
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:

module.exports = {
    nasa_api_key:           /* api key received from NASA */
        'NASA_API_KEY_STRING',
};
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.

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 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.
Change the api_url value appropriately in the app/src/main/res/values/config.xml file:

<resources>
    <string name="api_url">http://10.0.2.2:8080/api.nasa.gov</string>
</resources>
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:
public class PhotoRequester {

    // ...
  
    public void getPhoto() throws IOException {

        // grab the current calendar date and back up one day for next request

        final String date = mDateFormat.format(mCalendar.getTime());
        mCalendar.add(Calendar.DAY_OF_YEAR, -1);

        // build and call the request

        String urlRequest = mContext.getString(R.string.api_url) +
                BASE_PATH + DATE_PARAMETER + date;
        
        /*    ************ REMOVE API_KEY FROM urlRequest *************
        urlRequest += API_KEY_PARAMETER + mContext.getString(R.string.api_key);
        */
        
        final Request request = new Request.Builder().url(urlRequest).build();
        mLoadingData = true;

        mClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
              // ...
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
              // ...
            }
        });
    }
}
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.

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 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:

module.exports = {
  nasa_api_key:         /* api key received from NASA */
      'YOUR NASA API KEY STRING HERE',
  approov_token_secret: /* token secret received from Approov demo download */
      'APPROOV DEMO TOKEN SECRET BASE64 STRING HERE',
};
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:

module.exports = {
    proxy_port:             /* port the proxy listens on */
        8080,
    nasa_host:              /* NASA API host */
        'api.nasa.gov',
    nasa_protocol:          /* NASA API protocol */
        'https:',
    approov_header:         /* Approov header name */
        'approov',
    approov_enforcement:    /* set true to enforce token checks */
        true,
};
The original index.js file contained a pre-process all requests route handler:

// preprocess all proxy requests

app.use((req, res, next) => {
  // just pass through each request

  //log_req(req);

  next();
});
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:
// load and check approov token checking

const approov = require(`${__dirname}/approov`);

if (config.approov_header == null) {
  throw new Error(`approov_header not found; please set in ${__dirname}/config.js`);
}
const approovHdr = config.approov_header;

// preprocess all proxy requests

app.use((req, res, next) => {
  // check and delete approov token

  //log_req(req);

  var token = req.headers[approovHdr];
  delete req.headers[approovHdr];

  if (!approov.isValid(token)) {
    console.log(chalk.red('Unauthorized: invalid Approov token'));
    res.status(401).send('Unauthorized');
    return;
  }

  next();
});
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.

Add Client Attestation

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:
package com.criticalblue.android.astropiks;

import android.app.Application;
import android.util.Log;

import com.criticalblue.attestationlibrary.ApproovAttestation;
import com.criticalblue.attestationlibrary.ApproovConfig;
import com.criticalblue.attestationlibrary.TokenInterface;
import com.jakewharton.picasso.OkHttp3Downloader;
import com.squareup.picasso.Picasso;

import java.io.IOException;
import java.net.MalformedURLException;

import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 * Represents the astropix application.
 *
 * This is only used to hold a long running Approov attestation object
 * throughout the running activities.
 */
public class App extends Application {
    final static String TAG = "APP";

    OkHttpClient mClient = null;
    Picasso mDownloader = null;

    /**
     * Adds Approov attestation token to http requests.
     */
    private class ApproovInterceptor implements Interceptor {

        /**
         * Intercepts the http request and adds the approov token to the request headers.
         *
         * @param chain the request chain.
         * @return the augmented request.
         * @throws IOException on I/O error.
         */
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();

            TokenInterface.ApproovResults approovResults =
                    ApproovAttestation.shared().fetchApproovTokenAndWait(null);
            String token;
            if (approovResults.getResult() == ApproovAttestation.AttestationResult.SUCCESS) {
                token = approovResults.getToken();
            } else {
                token = "NOTOKEN";
            }
            request = request.newBuilder().addHeader("approov", token).build();

            return chain.proceed(request);
        }
    }

    @Override
    public void onCreate (){
        super.onCreate();

        // Initialize the Approov SDK
        try {
            // Creates the configuration object for the Approov SDK based
            // on the Android application context
            ApproovConfig config =
                    ApproovConfig.getDefaultConfig(this.getApplicationContext());
            ApproovAttestation.initialize(config);
        } catch (IllegalArgumentException ex) {
            Log.e(TAG, ex.getMessage());
        } catch (MalformedURLException ex) {
            Log.e(TAG, ex.getMessage());
        }

        mClient = new OkHttpClient.Builder()
                .addInterceptor(new ApproovInterceptor())
                .build();
        mDownloader = new Picasso.Builder(this)
                .downloader(new OkHttp3Downloader(mClient))
                .build();
    }

    /**
     * Returns a client for http requests.
     *
     * @return an http client.
     */
    public OkHttpClient getHttpClient() {
        return mClient;
    }

    /**
     * Returns an image downloader for http requests.
     *
     * @return an http downloader.
     */
    public Picasso getImageDownloader() {
        return mDownloader;
    }
}
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.

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.

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. You also add a simple passthrough proxy for the apod image requests. Replace the existing src/api/nasa.js file with:
const path = require('path');
const url = require('url');
const request = require('request');
const chalk = require('chalk');

// load api configuration and secrets

const config = require(`${__dirname}/../config.js`);

if (config.nasa_host == null) {
  throw new Error(`nasa_host not found; please set in ${__dirname}/../config.js`);
}
const api_host = config.nasa_host;

if (config.nasa_protocol == null) {
  throw new Error(`nasa_protocol not found; please set in ${__dirname}/../config.js`);
}
const api_protocol = config.nasa_protocol;

const secrets = require(`${__dirname}/../secrets.js`);

if (secrets.nasa_api_key == null) {
  throw new Error(`nasa_api_key not found; please set in ${__dirname}/../secrets.js`);
}
const api_key = secrets.nasa_api_key;

const apodHostname = 'apod.nasa.gov';
const apodRoute = '/' + apodHostname + '/apod/image/*'
const apodDirect = api_protocol + '//' + apodHostname + '/';
const apodDirectRe = new RegExp(apodDirect.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'g');

/**
 * Describes NASA API route handlers.
 *
 * @param app the express app.
 */
function routes(app) {

  // proxy a picture of the day request

  app.use(`/${api_host}`, (req, res, next) => {
    console.log('Processing NASA API request');

    // build redirected request

    var urlInfo = url.parse(req.url, true);  // build proxied url
    urlInfo.protocol = api_protocol;
    urlInfo.host  = api_host;
    // urlInfo.pathname is req.url
    delete urlInfo.search;
    urlInfo.query.api_key = api_key;         // add nasa api key
    var nasaUrl = url.format(urlInfo);

    var proxyHome = req.protocol + '://' + req.headers.host;
    var apodProxy = proxyHome + '/' + apodHostname + '/';

    var nasaHdrs = req.headers;              // reuse most headers
    delete nasaHdrs['host'];
    delete nasaHdrs['accept-encoding'];

    // start proxy request

    request({ url: nasaUrl, headers: nasaHdrs }, (err, proxyRes, proxyBody) => {
      if (err) {
        console.log(chalk.red(`Internal Server Error: in NASA proxy: ${err}`));
        res.status(500).send('Internal Server Error');
      } else {

        // patch response to redirect any apod image requests through proxy

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

        res.writeHead(proxyRes.statusCode, proxyRes.headers);
        res.write(proxyBody);
        res.end();
      }
    });
  });

  // proxy a picture of the day image download

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

    // console.log('image headers:', JSON.stringify(req.headers, null, '  '));

    // pipe the image request through the proxy

    let proxyUrl = api_protocol + '/' + req.url;
    let proxyReq = request(proxyUrl);

    req.pipe(proxyReq).pipe(res);
  });
}

module.exports = { routes: routes };
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.

Going Forward

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.

Written by skiph | Developer and Advocate — Software Performance and API Security
Published by HackerNoon on 2017/05/04