Locking down your API_KEY and why that is important

Written by liliana | Published 2019/08/26
Tech Story Tags: javascript | typescript | npm | tmdb | webpack | api_key | keys | programming

TLDR Using ES6, TypeScript, and webpack, you can create API_KEY as a global constant that can be configured at compile time. This solution would work in any other setting that uses webpack or ES6. The key won't be completely hidden when the app is running, but it can be used in production mode. The solution is simple to set the key for each deployment script to run the app in development mode and production mode, using webpack's HMR (Hot Mode Reload)via the TL;DR App

Do you leave your keys inside your car in a big parking lot? If not, then why do you expose your API_KEYs in your Github projects?
Deploying apps using Cloud services is the norm, but it comes with security complexities. Cloud services require credentials, often in the form of API tokens. Sneaky hackers search for these tokens to be used as computing resources for mining cryptocurrency or use them to access sensitive data.
A very common practice is scanning the web and public tools such as GitHub in the search of API keys which are unknowingly publicly accessible. This presents significant risks to both users and cloud service providers.

The main problem is that historically devs store api_keys in the code

The code below is not that difficult to find,
# src/keys/api_key.js
SOME_SERVICE_API_KEY = '2es8-473t-43ef-a34d' # Don't tell anyone!
Many developers constantly expose their API keys in the code, then they push into their repo, and voilà!
Obviously, this is the least safe method, as you are disclosing an important secret, which highly increases the risk that your secret could leak.
I had to develop a project as an assignment, and one of the requirements was to consume "The Movie Database API" services. The way to do it is by providing the API_KEY in each call to the service. In my case, the key won't be completely hidden when the app is running.
Anyway, it represented a good opportunity for me to learn how to avoid storing the key in the code itself. I took the opportunity to share my experience here. You can get all the details in my GitHub project.
I implemented the assignment using ES6, TypeScript, and webpack, just note that this solution would work in any other setting that uses webpack.

Add the API_KEYS using webpack

In my case, I wanted a very simple solution where I would just set the API_KEY for each deployment script.
I used the webpack's
DefinePlugin
 , which allowed me to create the API_KEY as a global constant that can be configured at compile time.
new webpack.DefinePlugin({
  // Definitions...
});
This definition in my webpack.config file looks like this:
module.exports = env => {
  ...    
  plugins: [
      new webpack.DefinePlugin({
        'process.env.apiKey': JSON.stringify((env && env.apiKey) || ''),
     }),
  ],
Note that setting 
module.exports
 with a function, instead of assigning directly the object, is important. Otherwise, you don't get access to the 
env
variable.

Let's go deep into the configuration file

The configuration should be able to serve the app both in development and production mode. Of course, if you are using webpack, it also includes HMR (Hot Mode Reload).
This is how serving the app in development mode:
TMDB_API_KEY=<yourTmdbApiKey>
npm run serve-dev  -- --env.apiKey=${TMDB_API_KEY}
And, serve it in production mode:
TMDB_API_KEY=<yourTmdbApiKey>
npm run build:webpack -- --env.apiKey=${TMDB_API_KEY} && \
 npm run serve-prod
Please note that in both cases I need to tell webpack explicitly that I am passing the key. See that
--
 as an argument on its own is standardized across all UNIX commands. It means that further arguments should be treated as positional arguments, not options.
In this context, it means that the parameters after the -- should be sent to the command 
serve-dev
 or 
build:webpack
, depending on the script you are running.
I added the following code at the beginning of my webpack.config file, to make sure that whoever runs the app is aware the API_KEY is needed to run the scripts and load the app.
if (!env || !env.apiKey) {
    throw new Error(
      'You need to specify your tmdb api-key. You can do so by specifying ' +
        '--env.apiKey=<yourkey> in the command line. For example:\n' +
        '$ npm run serve-dev  -- --env.apiKey=<yourkey>\n' +
        ' or \n' +
        '$ npm run build-webpack -- --env.apiKey=<yourkey> && npm run serve-prod',
    );
  }
At this point, I have everything ready in my config file. I am importing this module and returning the API_KEY that I get after running the commands.
I have the following code in the api_key.js file that is located where I am consuming the service.
export function getApiKey(): string {
  if (!process.env.apiKey) {
    throw new Error(
      'You need to specify your tmdb api-key. You can do so by specifying ' +
        '--env.apiKey=<yourkey> in the command line. For example:\n' +
        '$ npm run serve-dev  -- --env.apiKey=<yourkey>',
    );
  }
  return process.env.apiKey;
}
And the scripts my package.json looks like…
"serve-dev": "webpack-dev-server --mode=development --config webpack.config.js",
"build:webpack": "webpack --mode=production -p --config webpack.config.js",
"serve-prod": "serve dist",
"test": "jasmine-ts --config=spec/support/jasmine.json",
"test-watch": "nodemon --ext ts --exec 'npm run test'",

But, what happens for the test scripts?

Normally we don't need the API_KEYS for testing, we should be mocking the calls. I know, but in my case, I have some code in the tests that indirectly calls the function that returns the API_KEY.
If you are in a similar situation, you can do the same as I did. In order to avoid the use of a proper API_KEY I added to babel.js this code:
// Global variables needed for api_key.ts.
// The function getApiKey() is not really needed for the tests (we mock the
// access to tmdb), but it is executed as part of the instantiation of the
// Injector class.
process = process || {};
process.env = process.env || {};
process.env.apiKey = 'dummy';
Now just run the npm test script without passing the apy_key:
npm run test
That's is all you need and now you should be ready to run your app, without specifying your api_keys in the code.
If you have any suggestions or ideas please let me know.

Written by liliana | FullStack developer
Published by HackerNoon on 2019/08/26