KyleJB
Posted on August 18, 2020
Welcome to my first blog post! I’d like to kick things off with a discussion on an important topic and propose an easy-to-implement solution to enable us to get started on what matters — our projects — while protecting ourselves on the internet.
This post will contextualize API key management on the presumption that you write in Ruby. However, the concepts herein should be applicable to any language.
Table of Contents
1. Why should I care?
We’re ready to move from “Hello World!” to our first project, but how do we start? Learning how to program is a daunting task. Take it from me; I started this journey three weeks ago and I thought I had — at least — the basic best practices committed to memory when I jumped into building my first project: The Commuter (interfaces with NYC’S MTA API via CLI). Unexpectedly, the biggest obstacles that I have faced to date pertain to everything but programming.
If your first foray into this world was anything like mine, coding best practices were easily forgotten in the face of obstacles. In my situation, I was wrestling with tokens and how to pass their value to the server in order to gain access to their data. I resigned to hardcoding the tokens in my code in order to distill the problem.
Did you happen to instinctually bristle when I admitted to hardcoding my token in my application file? Why? Maybe it was because we were taught dynamic code is better than static code? Or, maybe what I did goes against common sense when dealing with private information — you wouldn’t write your username and password to your email address on a homework assignment after all, right? Regardless of your reason, haven’t we all found ourselves in this situation — hardcoding some variable to help solve a complex problem?
Although there may be a better way (do let me know), I would think this is a reasonable approach to take provided that we refactor our code after testing is complete. However, are you making commits on git (e.g., git add .
, git commit -m “yay, api works"
)? Perhaps, you are also pushing that repository onto GitHub for your portfolio or to collaborate with others? Are you completely sure that you did not include those secret tokens within one the many commits or pushes to GitHub?
…hundreds of thousands of API and cryptographic keys leaked at a rate of thousands per day.¹
Researchers at North Carolina State University (NCSU) — Michael Meli, Matthew R. McNiece, Bradley Reaves — discovered that “even if commit histories are rewritten, secrets can still be recovered.” And, over the course of their study, many repositories publicly expose their key — like I almost did. And thanks to search indexing by companies like Google, it would have taken — on average — 20 seconds for my key to be discoverable through a search query that these researchers utilized.¹
While we cannot say from this analysis alone why cryptographic keys are leaked, it is clear that the poor practice of embedding secrets in code is a major root cause.¹
Because deleting those branches, files, or editing that line of code now would be too late, we need to set up our project folder correctly. And, on a brief tangent, I also recommend you take a look at Daniel Hahm’s Beginner’s Look at Git if you want to git better.
2. dotenv
to the rescue
There are dozens of ways to handle the process. If you’re new to programming and are learning Ruby, I recommend the gem ‘dotenv’; this is easiest to implement and gets the job done.
Other programming languages have similar libraries that go by different names, but these all expect our file structure to incorporate a file (not folder) called .env
that stores our secret environment variables, which we can then invoke in our code base as a variable. But, before proceeding further, add .env
to your list in .gitignore
. This will ensure that git will never add, commit, and/or push your .env
file, thereby preventing public API exposure as long as you only keep the secret values hardcoded to the .env
file.
3. Walkthrough — from setup to implementation
- Step 1 — install the ‘dotenv’ gem:
Add gem "dotenv"
into your Gemfile and don’t forget to run bundle install
after doing so.
- Step 2 — create your
.env
file:
While your terminal is in your project directory, touch .env
to create the file. If you’re using VSCode like me, the file will appear in root of your project:
.env
is known as a “global file” to the dotenv
gem and expects to find in the main folder of your project.
This is the only file where you should be writing your API key values and setting them equal to a CONSTANT (in Ruby, capitalized variables are constants — not global). Here’s an example of what this should look like:
Don’t worry… this isn’t my real API token. :)
You can define as many environment variables and values as you wish here. You can access the value of your key like so: ENV["YOUR_VAR_NAME"]
. For example, if I wanted to use my API key, as depicted in the example above, I would write ENV["MTA_KEY"]
in another file. But, none of this will work until we complete step 3: requiring the gem and making sure the gem loads so that it can access the locally defined environment variable we created here elsewhere in our project folder.
- Step 3— file configurations:
:: **MOST IMPORTANT → create or update your .gitignore
file and add .env
to the list like so:**
Note .env
on line 15; this ensures that git
commands will never add, commit, or push this file with your secret keys to the public domain.
:: SECONDARY CONFIGURATIONS → making dotenv
accessible within our project directory:
require 'dotenv'
Dotenv.load
In my app, class MtaRunner is the only model that requires access to my MTA API key, so this would suffice.
If you’ve built a custom class (learn more about classes here by Michael Horowitz) for your API calls, you may add the aforementioned lines above your class definition as depicted on the left. Alternatively, you may also add the aforementioned lines of code into your environment.rb
file provided that your project file is linked up to all your necessary folders/files properly; the benefit of adding the two lines to your environment.rb
file is you can access your key in all your models without needing to write require 'dotenv'
on each file.
If you want the ability to use dotenv when running rake console
to debug or test your API, I wrote this bit of code in my Rakefile
:
The change was done on line 3 and line 7. For any task(s) that may require dotenv
, you can follow a similar pattern to ensure it runs concurrently with that desired task.²
Citations
[1]: Michael Meli, Matthew R. McNiece, and Bradley Reaves. (February 2019). How Bad Can It Git? Characterizing Secret Leakage in Public GitHub Repositories
[2]: Rubydoc. dotenv Documentation. https://www.rubydoc.info/gems/dotenv/2.0.0#sinatra-or-plain-ol-ruby
Posted on August 18, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.