tl;dr Don’t hardcode secret tokens. Load them from the environment like this…
/config/initializers/secret_token.rb1234567891011 |
|
… and use the Dotenv gem in production if needed.
Insecure defaults
When you create a new Rails project, one of the files created will be /config/initializers/secret_token.rb
. This file will look something like this:
1234567 |
|
This token is used to sign cookies that the application sets. Without this, it’s impossible to trust cookies that the browser sends, and hence difficult to rely on session based authentication.
Why this is bad
Firstly, hard-coding configuration conflates config and code. Although this may not cause much pain in a very simple context, as the application and infrastructure grow this anti-pattern will make configuration increasingly complex and error prone.
An app’s config is everything that is likely to vary between deploys (staging, production, developer environments, etc). … Apps sometimes store config as constants in the code. This is a violation of twelve-factor, which requires strict separation of config from code. Config varies substantially across deploys, code does not.
The Twelve-Factor App III. Config
Security risk
More importantly though is the security implication. Knowing the secret token allows an attacker to trivially impersonate any user in the application.
The only system that needs to know the production secret token is the production infrastructure. In this case, the attack vector is limited to the production infrastructure, which is likely to be the most secure part of the infrastructure anyway.
By hardcoding the production secret token in the code base, the following attack vectors are opened:
Every developer that has had access to the code base
Every development workstation that has a local copy of the code
The source control repository (whether private or 3rd-party e.g. Github)
The continuous integration server
Any 3rd-party services that have access to the source code, e.g. Code Climate or Gemnasium
The people involved with all of the above services
If an attacker wishes to obtain the application’s secret token, there are vastly more opportunities when the secret token is stored in the code.
The prevalence of this bad practice can be seen by searching Github or Google. It’s trivial to gain administrative access to many live applications simply by browsing those search results.
Update: Bryan Helmkamp helpfully notes below that with these tokens it’s actually possible to execute arbitrary code on the web server.
Loading Rails configuration from the environment
In order to set the secret token securely, we want to load it from the application’s environment. The simplest method is to replace the hardcoded token with a reference to Ruby’s ENV
:
1234567 |
|
While this has the advantage of maintaining development/production parity, it can be inconvenient for simple apps. If ENV['SECRET_TOKEN']
isn’t set locally — for example in the development or testing workflow — ActionDispatch will raise an exception like:
ArgumentError (A secret is required to generate an integrity hash for cookie session data. Use config.secret_token = "some secret phrase of at least 30 characters"in config/initializers/secret_token.rb):
One solution to this is managing a full set of environment variables within the development and test workflows. See below for more details on this.
Alternatively, a token could be hard-coded for the development
and test
environments, and loaded from the ENV in production
.
1234567891011 |
|
This also removes any pretense that the hard-coded token is secure.
Occasionally, the following solution is used:
/config/initializers/secret_token.rb — Don’t do this!1 |
|
However, if ENV['SECRET_TOKEN']
isn’t set in production, this will use the insecure token with no warning.
Managing an Application’s ENV
Dotenv is an excellent gem for managing an application’s environment. Heroku’s foreman uses this behind the scenes. Install it with:
Gemfile1 |
|
By default, it loads environment variables from the .env
file. Simply create this file in the RAILS_ROOT
on the production web server.
12 |
|
As the application configuration and infrastructure grows more complex, the gem also provides a consistent method to manage configuration across multiple developers, CI, staging and production servers. Brandon Keepers wrote more on the rationale for the gem.
Heroku
On Heroku, the application’s environment variables are managed from the heroku
CLI:
$ heroku config:set SECRET_TOKEN=3eb6db5a9026c547c72708438d496d942e976b252138db7e4e0ee5edd7539457d3ed0fa02ee5e7179420ce5290462018591adaf5f42adcf855da04877827def2
(or you could use something like the HerokuConfigVars engine)