Brian Hays

~ Ruby on Rails, Crossfit and other awesomeness

Better Secret_Token Management for Rails

When creating a shiny new Rails app, one of the files automatically created for your application’s security is config/initializers/secret_token.rb. The content includes a long randomized string which is used to verify the integrity of signed cookies (such as user sessions when people are signed into your web app). Signed cookies and verifying integrity is great right?? Well, yes but here’s the problem…

The Problem

The issue lies in the fact that many developers may not be aware the secure_token.rb file even exists, or if it exists, that it can render their application insecure by default. It is not included in the .gitignore file nor should it be (at least not by default) because your application “needs” that token in order to properly function. Because of this, the file ends up getting shared on GitHub or other publicly accessible repositories. An attacker can then use your secret token against you to impersonate any user on your application.

The Solution

There are two solutions I have used to secure the secret token. Both do an excellent job of keeping the secret_token a “secret” but the second solution is my preferred option as it provides a better experience for the user as detailed below:

Solution 1: After having created several Rails apps over the past couple years it wasn’t until I recently began going back through Rails books and tutorials in earnest that I came across the secure_token issue. Chapter 3 of Michael Hartl’s excellent Rails Tutorial details the issue and provides the solution by dynamically generating the secret token, storing the actual token in a .secret file. You then manually add .secret to your .gitignore file so the contents never appear on any shared repositories. The solution looks like this:

(credit - railstutorial.org) config/initializers/secret_token.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Be sure to restart your server when you modify this file.

# Your secret key is used for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!

# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
# You can use `rake secret` to generate a secure secret key.

# Make sure your secret_key_base is kept private
# if you're sharing your code publicly.
require 'securerandom'

def secure_token
  token_file = Rails.root.join('.secret')
  if File.exist?(token_file)
    # Use the existing token.
    File.read(token_file).chomp
  else
    # Generate a new token and store it in token_file.
    token = SecureRandom.hex(64)
    File.write(token_file, token)
    token
  end
end

SampleApp::Application.config.secret_key_base = secure_token

The above thoroughly solves the security issue related to keeping your token a secret. However, with it’s enhanced security it can create a disturbance for users that are logged in to your app (the culprit lies in line 4 of the code pasted above … “If you change this key, all old signed cookies will become invalid!”). Each time you deploy a new version of your app the .secret file will be regenerated in your production environment and render all previous cookies invalid. This means all users will need to sign back into your app each time you deploy new code.

Solution 2: My preferred solution for managing the secret token in a Rails app is to use environment variables. This may “sound” like a more difficult approach, but it is just as easy as Solution 1. Also, there’s the added benefit of having zero negative impact on your users whatsoever. Here we go, step-by-step:

  1. add the Figaro gem to your Gemfile for easy ENV variable configuration:

    Gemfile
    1
    
    gem "figaro"
    

    then run bundle install

  2. run the Figaro installer like so:

    1
    
    rails generate figaro:install
    

    this will create the file config/application.yml and ignores it in your .gitignore

  3. open your config/initializers/secret_token.rb file. The contents should look similar to the following depending on whether your app is using Rails 3 or Rails 4:

    Rails 3:

    Rails 3 – secret_token.rb
    1
    2
    3
    4
    5
    6
    7
    
    # Be sure to restart your server when you modify this file.
    
    # Your secret key for verifying the integrity of signed cookies.
    # If you change this key, all old signed cookies will become invalid!
    # Make sure the secret is at least 30 characters and all random,
    # no regular words or you'll be exposed to dictionary attacks.
    DemoApp::Application.config.secret_token = '02b537e328b31ab0524f333624d95045a0fe830240f33d9b542a652abb2bee5ffa59978d66531fcda8a97e62293a40ef8a045a5ed5c5965e7b8d31f7ee812060'
    

    Rails 4:

    Rails 4 – secret_token.rb
    1
    2
    3
    4
    5
    6
    7
    
    # Be sure to restart your server when you modify this file.
    
    # Your secret key for verifying the integrity of signed cookies.
    # If you change this key, all old signed cookies will become invalid!
    # Make sure the secret is at least 30 characters and all random,
    # no regular words or you'll be exposed to dictionary attacks.
    DemoApp::Application.config.secret_key_base = '9489b3eee4eccf317ed77407553e8adc97baca7c74dc7ee33cd93e4c8b69477eea66eaedeb18af0be2679887c7c69c0a28c0fded0a71ea472a8c4abf3f0a19cb'
    

    Change the long string of numbers after the = sign to ENV['SECRET_TOKEN']

    The line in your secret_token.rb file should now look like:

    Rails 3:

    Rails 3 – secret_token.rb
    1
    
    DemoApp::Application.config.secret_token = ENV['SECRET_TOKEN']
    

    Rails 4:

    Rails 4 – secret_token.rb
    1
    
    DemoApp::Application.config.secret_key_base = ENV['SECRET_TOKEN']
    

  4. Open the config/application.yml file that was created by figaro in step 2. Within this file you will set your secret token variable. To generate a new, secure token you can run the command rake secret from within your app directory. If this method generates ‘missing gem’ errors you can also run the following on Mac or Linux as long as you have openssl installed openssl rand -hex 64

    Copy the key generated by either of the commands above. Then, in your application.yml file type SECRET_TOKEN: paste_your_gererated_key_here

    it should now look similar to the following (be sure you are using your own key and not copying the one below):

    config/application.yml
    1
    2
    3
    
    # Add application configuration variables here, as shown below.
    #
    SECRET_TOKEN: 9489b3eee4eccf317ed77407553e8adc97baca7c74dc7ee33cd93e4c8b69477eea66eaedeb18af0be2679887c7c69c0a28c0fded0a71ea472a8c4abf3f0a19cb
    

  5. At this point you can add and commit your changes via git. When pushing the code to your repository, the secret token will no longer be visible since the application.yml is automatically included in the .gitignore file

    IMPORTANT: The step below MUST be completed in order to set the Environment variables within your production environment. If you deploy your changes to production but do not follow the step below, your application will show an error page

  6. Final Step: set up the Environment variables in production

    This final step is essential and also extemely easy (especially if you are using Heroku)

    To set up your SECRET_TOKEN environment variable on Heroku, the figaro gem provides a rake task to do just that. Simply run:

    1
    
    rake figaro:heroku
    

    That’s it. You’re ready to go. You can view/verify the setting on Heroku by running heroku config from your command line

    If you ARE NOT using Heroku just add a copy of config/application.yml to your production app on the server.

  7. Your “secret token” is now not only secure but will also persist during future deployments.

Comments