tl;dr Don’t hardcode secret tokens. Load them from the environment like this…
1 2 3 4 5 6 7 8 9 10 11
… and use the Dotenv gem in production if needed.
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:
1 2 3 4 5 6 7
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.
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.
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
1 2 3 4 5 6 7
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
test environments, and loaded from the ENV in
1 2 3 4 5 6 7 8 9 10 11
This also removes any pretense that the hard-coded token is secure.
Occasionally, the following solution is used:
ENV['SECRET_TOKEN'] isn’t set in production, this will use the insecure token with no warning.
Managing an Application’s ENV
By default, it loads environment variables from the
.env file. Simply create this file in the
RAILS_ROOT on the production web server.
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.
On Heroku, the application’s environment variables are managed from the
$ heroku config:set SECRET_TOKEN=3eb6db5a9026c547c72708438d496d942e976b252138db7e4e0ee5edd7539457d3ed0fa02ee5e7179420ce5290462018591adaf5f42adcf855da04877827def2
(or you could use something like the HerokuConfigVars engine)