TL;DR: Environment Variables
API keys are one example of sensitive information that should remain secret, the problem is that we need to use them in our code to access third-party services like Twitter, Github, DigitalOcean and so on, so how do we manage to use those API keys without hard-coding them into the source code?
The twelve-factor app stores config in environment variables (often shortened to env vars or env). Env vars are easy to change between deploys without changing any code; unlike config files, there is little chance of them being checked into the code repo accidentally; and unlike custom config files, or other config mechanisms such as Java System Properties, they are a language- and OS-agnostic standard. — Twelve-Factor App On Configuration
The answer is: Environment Variables. This is based on the Twelve-Factor App methodology which I recommend you to read, it is an excellent essay that will teach you the process of Software-as-a-Service (SaaS). Let’s dive into it using Python and Ruby as an example.
Why this matters
Let’s put this short and easy, let’s say that we have two type of credentials to access certain web service:
- an API id = ‘846a4da4d06as84d6as84d06’
- and a SECRET id = ‘secret_id_so_secret’
If we want to use those credentials we could hard code them into our software like this
# Setting credentials
api_id = '846a4da4d06as84d6as84d06'
secret_id = 'secret_id_so_secret'
# login function
def login(api_key, secret):
print('Logged with:')
print('API: ' + api_id)
print('SECRET: ' + secret_id)
# Logging in to third party app with hard-coded credentials
login(api_id, secret_id)
That would be extremely wrong because anyone that is able laid eyes on your code will have the power to steal your credentials and access sensitive information about you, your software and even Personally Identifiable Information (PII) about your customers, your team, or anybody who uses your software.
But, how do we remove these credentials from our code and use them as environment variables?
Securely Storing Credentials in Python
python-dotenv
is a very easy to use package that reads a key=value
pair from a text file .env
(hence dotenv) and load those variables to your environment variables so you can use your API keys securely in your code. You can install python-dotenv
with pipenv.
Now let’s move the API credentials from our code to a safe .env
text file:
Add a Git exclusion for .env
First we must add an exclusion to our .gitignore
in git so they are not uploaded to Github, commit your exclusions and you’re ready to fill your .env
credentials
echo ".env" >> .gitignore
git commit .gitignore -m "add exclusion for .env"
Install python-dotenv
with Pipenv
$ pipenv install python-dotenv
# Output
Installing python-dotenv…
-- SNIP --
Adding python-dotenv to Pipfile's [packages]…
Pipfile.lock not found, creating…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (446908)!
Installing dependencies from Pipfile.lock (446908)…
🐍 ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 2/2 — 00:00:00
To activate this project's virtualenv, run the following:
$ pipenv shell
Adding credentials
Now let’s strip our credentials from our code and add them to our dotenv file:
# .env file
api_id = '846a4da4d06as84d6as84d06'
secret_id = 'secret_id_so_secret'
Loading credentials to our environment variables
Let’s modify our code to add the python-dotenv
package
# import functionality to find and load dotenv credentials
# and getenv to get environment variables from OS
from os import getenv
from dotenv import load_dotenv, find_dotenv
# load environment keys, it will automatically find and load .env
load_dotenv(find_dotenv())
# login function
def login(api_key, secret):
print('Logged with:')
print('API: ' + api_id)
print('SECRET: ' + secret_id)
# Logging in to third party app with environment variable credentials
api_id = getenv('api_id')
secret_id = getenv('secret_id')
login(api_id, secret_id)
Now when we run our code we will successfully access our service with our credentials without compromising our keys
# Output
Logged with:
API: 846a4da4d06as84d6as84d06
SECRET: secret_id_so_secret
Ruby Example
Add dotenv
to your Gemfile
and remember to add your credentials to your .env
file
source 'https://rubygems.org'
gem 'dotenv'
Import it and use it in your code as follows
require 'dotenv'
# load dotenv credentials
Dotenv.load
api_id = ENV['api_id']
secret_id = ENV['secret_id']
puts "API: #{api_id}"
puts "SECRET: #{secret_id}"
Now execute your code
bundle exec ruby test.rb
# Output
API: 846a4da4d06as84d6as84d06
SECRET: secret_id_so_secret