DEV Community

Benjamin Mock
Benjamin Mock

Posted on • Originally published at codesnacks.net

How to handle secrets in Node.js πŸ—οΈπŸ—οΈπŸ—οΈ (environment variables)

What's the best way to handle configurations or secrets like API keys with Node.js? One simple way is to use environment variables.

You could pass them directly to your node command or add them to the package.json. Let's have an index.js, that looks like

const apiKey = process.env.API_KEY
console.log(apiKey)
Enter fullscreen mode Exit fullscreen mode

You can, for example, start your application with

API_KEY=super-secret-key node index.js
Enter fullscreen mode Exit fullscreen mode

You can also put the same into your package.json

  ...
  "scripts": {
    "start": "API_KEY=super-secret-key node index.js"
  },
  ...
Enter fullscreen mode Exit fullscreen mode

and start your application with npm start. That way you at least don't have to type your API key every time you start your application.

The problem with this approach is, that you have to commit your package.json to your repository. But you should not share secret keys like this. So there's a better way to do it: using a .env file.

So you can add your API key to this .env file and consume it, like before, with the dotenv library.

run

npm install dotenv
Enter fullscreen mode Exit fullscreen mode

to install the library.

Then import and use it like this in your application:

require('dotenv').config();

const apiKey = process.env.API_KEY
console.log(apiKey)
Enter fullscreen mode Exit fullscreen mode

Your '.env` file will now contain your secret.


API_KEY=super-secret-key

Ideally, you would then also create an entry in your .gitignore to exclude your .env file from version control.

Put this in the .gitignore:


.env

This way you have all your secrets in one place and you don't accidentally leak any secrets.

Top comments (3)

Collapse
 
crussell52 profile image
Chris Russell

@benjaminmock, thanks for taking the time to share with us! This comment runs contrary to the advice in your post. It is not intended to judge you or discourage you from sharing with the community. It is only intended to educate on the not-discussed-enough risks of secrets in environment variables. ❀️

Environment variables are a popular and convenient way to configure an application and I encourage their use. But don't use them for your secrets.

The idea of using ENV for secrets has become prolific in the last few years. I see this recommendation, a lot. Maybe it is because of the quotability of "The Twelve-Factor App" which encourages ENV as the primary vehicle for configuration... or maybe it is because ENV is so convenient when it comes to containerized apps. But it is not good practice.

(A quick side-note, The Twelve-Factor App does not address secrets one way or another so it is not fair to say the author of that essay popularized the idea. I'm convinced that it added to the popularization of config-by-ENV. From there, I think people just made the leap to secrets because "secrets" have historically be considered part of "configuration.)

Why is this bad practice? Simply put, your secrets become globally available to the application (part of what makes it convenient!)... that means every package you bring in, directly or indirectly has access trivial access to your secret. A well-meaning package could simply output the entirety of the ENV in an error case to help with problem-identification and now you unexpectedly have secrets sitting in a log somewhere. A nefarious package could capture the entire ENV in search of secrets, perhaps even looking for specific key phrases in the env variables name.

Many application also spawn child processes. Unless you are careful about it, your ENV becomes the env of the child process. This inheritance behavior is fundamental to the env. Some security-minded programs will include mechanisms to prevent this inheritance (sudo does this) but most do not -- mainly because env inheritance is expected. But not for secrets. Try the following.

// parent.js
const {execFileSync} = require('child_process');
console.log('parent', process.env["FOO"]);
console.log(execFileSync('node', ['child.js']).toString());
Enter fullscreen mode Exit fullscreen mode
// child.js
console.log('child', process.env["FOO"]);
Enter fullscreen mode Exit fullscreen mode
// command-line
FOO="mySecret" node parent.js
Enter fullscreen mode Exit fullscreen mode

See points above about well-meaning, and bad-intentioned libs and extend that to other processes you may spawn.

Instead, put your secrets in a file and secure that file using appropriate access control. Load that file when you need the secrets.

At the end of the day, my advice is: Use environment variables for configuration. Do not use them for secrets.

P.S. Here's some other posts by not-me on this subject:

Collapse
 
fanoftheauthor profile image
S. E. Newton

thank you Benjamin

Collapse
 
purezero profile image
Kartika Prasad

Any thoughts on encrypting values in the dotenv file