DEV Community

Cover image for Hoarding Secrets in NodeJS
Aoibhe Wilson 🇨🇭
Aoibhe Wilson 🇨🇭

Posted on • Originally published at Medium

Hoarding Secrets in NodeJS

Originally posted at Valtech Switzerland on Medium

When working with NodeJS it's likely you'll wind up with a few secrets; not secrets like "Popeyes has the best fries" but API keys, encryption keys, database passwords, and other things you wouldn't want anyone else to learn.

These secrets are values you'll need to use in your code, but you never want to directly include them in the actual code, especially when you code is distributed to users, or if you use public repositories for your code. So how do we run these values in our code? Let's learn a bit about Environment Variables.

If you're already comfortable with environment variables you can jump right to implementation.

The Execution Environment

When you run code on a computer, whether your physical machine, or a virtual machine in the cloud, the operating system and configured software on that machine are your code's execution environment. When running Node scripts, information about the execution environment is made available through special variables. We call these environment variables.

On a physical server you might set these values directly in the operating system. If you're deploying code to a cloud environment there will usually be a preferences pane where you can set your environment variables, some are even set for you. But what about local development? You could set them in your system settings, your dev computer is, after all, an Execution Environment in itself. Setting the variables directly like this is tedious, however and can quickly get out of hand when you work on multiple projects.

There's a better, more flexible way; lets learn about…

.Env Files

A .env file is a text file that lives on your machine where you define key-value pairs. This file is then loaded by the Node runtime and the values are made available as environment variables. You can have a file for each project so you don't have to worry about mixing secrets or changing system variables when you change project. When you need a new variable you add it to the file and rerun your script.

Getting started won't take much but, these are your secrets we're talking about. You can, very easily, accidentally share them with the world. There are plenty of stories in dev and security circles of sites with publicly published encryption keys, admin log-ons, etc. proper care should be taken from the start.

So with that in mind…

Let's Use Some Secrets

Create a new folder for your project as you normally would. For me this means running the following in my terminal:

If you want to follow along with my code, you can clone the repository from Github. You'll just need to add your own .env file. No promises, but I may even add more examples as they come up.

Next is a very important file for guarding our secrets. Whether you use Git or not it's a good habit to create this file before you add a single secret to your project. This helps to prevent you or someone else who works on your project from sharing your secrets in public and preserving them in your project's Git history. If you're not familiar with Git, I'm talking about the Git Ignore file. This file tells Git – if it's ever initialised in your project – to ignore specific files. In this case we want Git to ignore our .env files.

Create the file .gitignore in the root of your new project and add the following content:

With that file saved in our root we're now safeguarded against the very common accident of committing our secrets to a code repository, we've even covered several formats for the file (though it's recommended to only ever have the one .env file). With that peace of mind we can set up and start using our .env files.

Using the DotEnv Package

I mentioned before that the .env file needs to be loaded into the node runtime. To do that I use a package named DotEnv. DotEnv is a well-maintained module that will load our .env file environment variables and make them available to our node process.

Install DotEnv as a Dev dependency now using either npm (what I'm using if you're following me) or yarn.

Now we need to set up our Node package script so that DotEnv is run and parses our environment variables. Open your package.json file and modify your scripts to look like this:

What we're doing in our 'dev' script is pre-loading dotenv and running the config before node runs our entry script. I prefer this method because we don't need to add any code that will only be used on our dev environment. Remember, these environment variables will be set by other means on our deployment environments; we only need DotEnv for our local development.

Creating our .Env File

With our .gitignore insurance plan in place and DotEnv ready to use, let's create our .env file and share some secrets.

Create the file named .env in your project root and add some secrets like so:

You can use string values directly, single quotes, double quotes, etc. For more on how the file is parsed you should review the parsing rules. You can have multi-line values, whitespace, empty rules, and a lot more.

With our .env file created now we can use these super-secret keys in our code.

Retrieve our Secrets

Let's create our 'index.js' file and read our variables in. Create the file and add the following code passages. We'll explain each one as we go.

No secrets slipping out here in the code but run npm run devand the truth is revealed.

So what happened here? DotEnv ran before our script and loaded all of our key-value pairs into an object set to the env property of our Node process. In the first line we access our chosen key and assign it to a constant. We now have the value of our secret ready to write out (just for our demonstration) without having to reveal it in our code. It stays safely in our .env file.

Let's add some more. Since process.env is an object, we can use destructured assignment to access a couple of our environment variables at once like so:

Run npm run dev again and 'Ta-Da' we've aired more of our secrets out in the open.

One more example: what if we need a default value for one of our environment files? A common example is the PORT value. On cloud environments especially the PORT that your script runs on is decided by the environment and the PORT value is set for you but if it's not, you still want a port number for your process to listen on. Let's do that like so:

This is known as 'short-circuit evaluation' we're using the logical 'OR' operator to assign the second operand if and only if the first is null (or falsy). So if we set the PORT .env variable, which we have, our script will report that it is listening on that value, 8081 in our case. If we didn't set the value, it would listen on 3000 instead.

That's it for the basics.

So, When Would I Use This?

When you're working in Node there are a lot of places you'll encounter a need for secrets.

Databases

To connect to a Database you'll need to provide a user, password, and a database name at a minimum. No one needs to know what those values are and having them get accidentally published to your public repository or out there in plain text on your server could be a disaster.

API Keys

Many times when we want to use an API for our development we will have an account and keys from the API provider. The API may limit our rates or we may be charged by usage. Anyone with our keys can make their own requests as us! Publishing API keys is a really good way to rack up a huge bill with your API provider. Even worse - the API could have full access to sensitive information like your collection of heirloom dad-jokes.

Environment Configuration

When you deploy your code to the cloud certain environment variables are going to be provided for you. PORT is a common example. Your provider will usually document what variables they make available for you and will also give you space to set your own. If you can set a default for these you don't really need to add them to your local .env file (reduce the risk) but now you know how to set them if you need them.

Configuration Without a Release

Not everything in your .env has to be a secret. One neat use for your environment variables is being able to change a setting in your code without needing to deploy a new version to your server. For example you could add an on/off switch for the new feature you're about to release. The code is deployed but with a flag tied to an environment variable. When marketing sends out the email to users you don't need to start a deployment, you just set your switch in your server environment and your new feature comes to life.

There are a lot of things that can be done here (but beware of the fact that you're not able to provide for version control on these settings).

That's It

So, first, I'm going to say it again. Before adding a .env file and any secrets add a .gitignore file with .env. Even if you don't use git initially. Protect yourself from making the same mistake all the rest of us have.

I hope you've got an overview of how to use Environment Variables in Node. There's a lot more out there you can dig into, the DotEnv README is a good start, but this should be enough to get you started and give you some basic protection against broadcasting your secrets to the world.

Recapping the Steps

  • Add a .gitignore file to your project root with a rule to ignore '.env' at a minimum

  • Install DotEnv (or a similar module) as a dev dependency and add the preloader to your package script

  • Add your secrets as key-value pairs in your .env file

Your environment variables are waiting in process.env. Good luck, and happy developing!

Top comments (0)