Storing API credentials for a Rails app
Hey there!
If you’re just starting out with an API, you’ve probably tossed your API credentials somewhere as a class constant, like this:
class NokoClient
PERSONAL_ACCESS_TOKEN = "abcd12345"
URL = "https://api.letsfreckle.com/v2"
include HTTParty
def get_entries
self.class.get("#{URL}/entries", :headers => {
"X-NokoToken" => PERSONAL_ACCESS_TOKEN
})
end
end
This is completely valid for quickly hacking and trying things out. But once you’ve got a handle on APIs, you’ll want to start securely storing API keys for a few reasons:
- Security: If your keys get checked into source control accidentally, they’re exposed and no longer a secret. Even if it’s a private repo, you’ll want to keep your keys closely guarded, since they’re the keys to the kingdom.
- Flexibility: Hard-coded values in a codebase are a pain to maintain. Let’s say that you need to regenerate your API keys. You’d have to dig through the code for every mention of the API key. It would be so much easier if your credentials were decoupled from your app and could be updated independently of the codebase.
- Isolation: It’s standard practice to keep your production API keys away from development. You might use different accounts, the service might give you different keys, or you might have registered the same app for each environment (e.g: “MyApp (Development)”). In any case, the API keys always need to be separate and only load for the right environment.
- Automated Deployment: As automated deployment becomes even more popular, you need a solution that seamlessly fits into your deployment scripts without mucking up the app’s repository with unrelated code.
The sanest approach that solves all these problems is to load the API credentials as environment variables in the app, which are stored in environment-specific YAML files. Thomas Fuchs introduced me to this pattern, and I’ve used it in all my apps since. The version below is for Rails 3+, let me know if you need a Rails 2.3 LTS version!
The code
# config/application.rb
module YourApp
class Application < Rails::Application
...
# Try loading a YAML file at `./config/env.[environment].yml`, if it exists
# Kudos to Thomas Fuchs (http://mir.aculo.us) for the initial implementation
def load_env_file(environment = nil)
path = Rails.root.join("config", "env#{environment.nil? ? '' : '.'+environment}.yml")
return unless File.exist? path
config = YAML.load(ERB.new(File.new(path).read).result)
config.each { |key, value| ENV[key.to_s] = value.to_s }
end
# Load environment variables. config/env.yml contains defaults which are
# suitable for development. (This file is optional).
load_env_file
# Now look for custom environment variables, stored in env.[environment].yml
# For development, this file is not checked into source control, so feel
# free to tweak for your local development setup. Any values defined here
# overwrite the defaults in `env.yml`
load_env_file(Rails.env)
end
end
Now to add the YAML files store our credentials!
# config/env.yml
# Just a test value to show overwriting
a: "hello"
# config/env.development.yml
# Just a test value to show overwriting
a: "derp"
freckle_personal_access_token: "abcd12345"
Now when we load the app, we’ll see these values loaded as environment variables!
$ rails c
irb(main):002:0> ENV['a']
=> "derp"
irb(main):003:0> ENV['freckle_personal_access_token']
=> "abcd12345"
Next up is to make sure the environment-specific YAML files are never stored in source control:
# .gitignore
# Ignore the environment-specific YAML files
/config/env.*.yml
Finally, we change our NokoClient
class to use the new environment variable and we’re good to go!
class NokoClient
URL = "https://api.letsfreckle.com/v2"
include HTTParty
def get_entries
self.class.get("#{URL}/entries", :headers => {
"X-NokoToken" => ENV['freckle_personal_access_token']
})
end
end
Benefits
This approach has a number of benefits for development, security, and automated deployment:
- Each developer can set their own environment variables for the specific features they work on.
- A default set of environment variables exists with
env.yml
, which can even be used as an example configuration file with dummy values. - Environment variables can be set as-needed and new environments can be painlessly setup.
env.travis.yml
could store all your environment variables for running tests on Travis. - Automated deployment scripts can generate their own
env.*.yml
files for the target environment. The app will seamlessly use these new changes and the app’s repo is not mucked up with automated deployment code. - Since production environment variables don’t need to be stored in the app’s repo, there’s less chance of an accidental commit.
Other Notes
- Each developer creates their own development API keys whenever possible. This stops devs from stepping on each other’s toes while working on a new feature or bugfixing.
- Production API keys are a closely guarded secret. A scrict “need to know basis” is the best approach here.
- Keep
env.yml
up-to-date as a template to make onboarding new devs easier. Seeing all the environment variables in one place makes it easier to get started.