API keys are crucial in web development as they allow applications to interact with external services like payment gateways, social media platforms, and other web services. However, API keys are sensitive information, and if they fall into the wrong hands, they can lead to unauthorized access to your application and data.
In this blog post, we will discuss how to securely store API keys in Ruby on Rails applications to ensure the safety of your application and data from malicious actors.
❌ Hard coded in your code
Storing API keys directly in your code is the worst way to do it, for several reasons.
Firstly, the key will be checked into your version control, making it available to anyone with access to your code forever.
Secondly, it's difficult to distinguish between different API keys for different environments without writing additional code. This means that any testing you do in your local development environment will interact with the same external system as the production application. If you're not careful, tests can also accidentally start interacting with your production API.
Finally, changing the key requires committing and deploying a new version of your application.
ApiClient.new(api_key: "1234asdf") # don't do this!
✅ Using environment variables
One of the most common practices for storing API keys in Ruby on Rails applications is to use environment variables. Environment variables are variables set in the operating system's environment and can be accessed by any application running on that system.
Environment variables are not checked into version control and can have different values on different systems. For example, you may use a different API key on your production and development environments. Setting environment variables is also supported by every deployment and CI service.
By default, Ruby loads all environment variables into the
ENV hash, which you can access like any other hash. There are various ways to access them, and they behave differently when the key is not set:
ENV["API_KEY"] # => returns `nil` if not set ENV.fetch("API_KEY") # => raises 'key not found: "API_KEY" (KeyError)' if not set ENV.fetch("API_KEY", "default") # => returns "default" if not set
Depending on how you want your code to behave in case of a missing variable, you can decide which option to use.
Managing environment variables on local development environments can be tedious. It's difficult to know in advance which variables are needed, and setting them manually when starting the server is also not ideal.
Fortunately, there are a couple of solutions to address this issue. One such solution is dotenv, which reads variables from a local
.env file. Dotenv offers several different ways to work with env files for different environments and has a convenient system for creating templates that can be checked into version control.
✅ Using Rails encrypted credentials
Rails 5.2 introduced encrypted credentials, a built-in feature that enables you to securely store API keys in an encrypted file (
config/credentials.yml.enc). This file can be safely checked into version control since it can only be decrypted using the ‘master key’.
It is essential to keep this key secure and avoid losing it, as it cannot be recovered. Rails also uses this file to store the ‘secret key base’ it uses to generate secret keys for browser sessions.
To set up or edit encrypted credentials, run:
bin/rails credentials:edit. This generates a master key (in
config/master.key) and opens your default editor with the unencrypted credential file. When you save the file, it is re-encrypted automatically. To read the credentials on your app or CI server, set the
RAILS_MASTER_KEY environment variable to the stored value.
Another advantage of using credentials is that you can nest them (since it’s just a regular
yml file), providing more structure.
secret_key_base: ... system: api_key: 1234
Rails.application.credentials.system.api_key Rails.application.credentials.system.api_key! # => raises 'KeyError: :api_key is blank' if not set
One downside is that changes to your credentials require you to commit and deploy your application every time.
Using Different Credential Files for Different Environments
Since Rails 6, it is possible to have different encrypted credential files for different environments. Each environment will have its own master key and set of credentials. However, you need to keep them in sync yourself. Rails will load the correct file based on the environment the application is running in at that moment.
As before, make sure to keep the keys themselves safe and only check the
.yml.enc files into your version control.
rails credentials:edit --environment production
If you set up separate production and development credential files, you can share the development key with everyone that has access to the code and keep the production key more secure by restricting access only to people who can also access other parts of the production system.
⚡ Tip: Enable diffing encrypted credential files in Git
To make your life a bit easier, you can set up Git to decrypt the credentials file for showing a diff. This is done by configuring Git to use a different command to diff credential files. Rails has a built-in command to set this up for you:
rails credentials:diff --enroll
✅ Using Encrypted Database Columns
Sometimes, you need to use API keys supplied by your customers or users. To store them securely, it's best to encrypt them in the database. Fortunately, Rails provides a built-in encryption feature in Active Record for this purpose. This feature automatically encrypts and decrypts the values and ensures that the unencrypted values do not appear in logs.
To set up encryption, run the following command (note that it also uses the encrypted credentials discussed earlier to store some secrets):
To define the encrypted field in your model, add the following to your model:
class User < ApplicationRecord encrypts :api_key end
After this, you can use the
api_key field like any other field in your application.
✅ Using a Secret Management Service
A secret management service securely stores and manages your secrets, like API keys, passwords, and other sensitive information, in an external system. Several secret management services are available, such as HashiCorp's Vault, Amazon Web Services' Secrets Manager, and Google Cloud's Secret Manager.
These services provide encryption, access control, and auditing capabilities to ensure that your secrets remain safe and are only accessible by authorized personnel. They are typically mandated in larger organizations with more strict and centralized security requirements. However, for smaller teams, using these services may be a bit excessive.