My ugly mug

Hi, I'm Thomas Cannon!

I work on Noko with Amy Hoy and Thomas Fuchs, and I teach people how to add an extra layer of polish to their work. Previously I worked for BMW and Clemson University, and have a bunch of smaller apps I tinker with on the side. Follow me on Twitter!

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:

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:

Other Notes

  1. 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.
  2. Production API keys are a closely guarded secret. A scrict “need to know basis” is the best approach here.
  3. 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.