Secrets & Credentials
Securely manage sensitive information like API keys, database passwords, and other secrets in your Rails application using Rails credentials system.
Rails Credentials System
# Edit credentials (Rails 6+)
rails credentials:edit
# Edit credentials for specific environment
rails credentials:edit --environment production
rails credentials:edit --environment staging
# Show credentials (for debugging - be careful!)
rails credentials:show
rails credentials:show --environment production
# config/credentials.yml.enc (encrypted)
# Edit with: rails credentials:edit
secret_key_base: your_secret_key_base_here
database:
username: myapp_user
password: super_secure_password
api_keys:
stripe:
publishable: pk_test_12345
secret: sk_test_67890
aws:
access_key_id: AKIAIOSFODNN7EXAMPLE
secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
sendgrid:
api_key: SG.your_sendgrid_api_key_here
oauth:
google:
client_id: your_google_client_id
client_secret: your_google_client_secret
github:
client_id: your_github_client_id
client_secret: your_github_client_secret
redis:
url: redis://localhost:6379/0
production:
database:
username: prod_user
password: production_password
# Accessing credentials in your application
class ApplicationController < ActionController::Base
private
def stripe_secret_key
Rails.application.credentials.api_keys.stripe.secret
end
end
# In models
class User < ApplicationRecord
def send_welcome_email
# Access nested credentials
api_key = Rails.application.credentials.api_keys.sendgrid.api_key
# Use the API key...
end
end
# In configuration files
# config/database.yml
production:
adapter: postgresql
username: <%= Rails.application.credentials.database.username %>
password: <%= Rails.application.credentials.database.password %>
# config/initializers/stripe.rb
Stripe.api_key = Rails.application.credentials.api_keys.stripe.secret
Master Key Security: The
config/master.key file must be kept secure and never committed to version control. It's needed to decrypt your credentials.
Environment-Specific Credentials
# Create environment-specific credential files
rails credentials:edit --environment staging
rails credentials:edit --environment production
# This creates:
# config/credentials/staging.yml.enc
# config/credentials/staging.key
# config/credentials/production.yml.enc
# config/credentials/production.key
# config/credentials/production.yml.enc
secret_key_base: production_secret_key_base
database:
host: prod-db.example.com
username: prod_user
password: production_secure_password
api_keys:
stripe:
publishable: pk_live_real_key
secret: sk_live_real_secret
aws:
access_key_id: PRODUCTION_ACCESS_KEY
secret_access_key: production_secret_access_key
redis:
url: redis://prod-redis.example.com:6379/0
password: redis_production_password
# Accessing environment-specific credentials
class PaymentService
def initialize
@stripe_key = if Rails.env.production?
Rails.application.credentials.api_keys.stripe.secret
else
Rails.application.credentials.api_keys.stripe.secret
end
end
# Or let Rails handle it automatically:
def stripe_key
Rails.application.credentials.api_keys.stripe.secret
end
end
# In initializers - Rails automatically uses environment-specific credentials
# config/initializers/redis.rb
$redis = Redis.new(url: Rails.application.credentials.redis.url)
Legacy Secrets (Rails 5.x)
# config/secrets.yml (deprecated but still supported)
development:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
api_key: <%= ENV["API_KEY"] %>
test:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
api_key: <%= ENV["API_KEY"] %>
# Accessing legacy secrets
Rails.application.secrets.secret_key_base
Rails.application.secrets.api_key
# Migration strategy from secrets.yml to credentials
# 1. Move secrets to credentials:
rails credentials:edit
# 2. Add your secrets to the credentials file
# 3. Update your code to use credentials instead:
# Old: Rails.application.secrets.api_key
# New: Rails.application.credentials.api_key
# 4. Remove config/secrets.yml
Migration from Secrets.yml
- Rails 5.2+ uses credentials system by default
- Credentials are more secure than secrets.yml
- Credentials are encrypted, secrets.yml relied on ENV vars
- Both systems can coexist during migration
Environment Variables Integration
# Combining credentials with environment variables
# config/initializers/app_config.rb
class AppConfig
def self.database_url
ENV['DATABASE_URL'] || Rails.application.credentials.database_url
end
def self.redis_url
ENV['REDIS_URL'] || Rails.application.credentials.redis.url
end
def self.stripe_secret_key
ENV['STRIPE_SECRET_KEY'] || Rails.application.credentials.api_keys.stripe.secret
end
end
# Using in application
class PaymentController < ApplicationController
def process_payment
Stripe.api_key = AppConfig.stripe_secret_key
# Process payment...
end
end
# .env file for development (with dotenv gem)
DATABASE_URL=postgresql://localhost/myapp_development
REDIS_URL=redis://localhost:6379/0
STRIPE_SECRET_KEY=sk_test_development_key
SENDGRID_API_KEY=SG.development_api_key
# Gemfile
group :development, :test do
gem 'dotenv-rails'
end
# config/application.rb
module MyApp
class Application < Rails::Application
# Load environment variables in development/test
if Rails.env.development? || Rails.env.test?
Dotenv.load('.env.local', '.env')
end
end
end
Security Best Practices
# Credential management in deployment
# 1. Never commit master.key or environment-specific .key files
# 2. Set master key as environment variable in production
export RAILS_MASTER_KEY="your_master_key_here"
# 3. Or create the key file securely on production server
echo "your_master_key_here" > config/master.key
chmod 600 config/master.key
# 4. For environment-specific keys
export RAILS_MASTER_KEY_PRODUCTION="production_master_key"
export RAILS_MASTER_KEY_STAGING="staging_master_key"
# Secure credential access patterns
class SecureConfig
# Lazy loading credentials
def self.stripe_secret_key
@stripe_secret_key ||= Rails.application.credentials.api_keys.stripe.secret
end
# Fallback for missing credentials
def self.redis_url
Rails.application.credentials.redis&.url || 'redis://localhost:6379/0'
end
# Environment-specific access
def self.database_password
credentials = Rails.application.credentials
case Rails.env
when 'production'
credentials.database.production.password
when 'staging'
credentials.database.staging.password
else
credentials.database.development.password
end
end
end
Security Warnings:
- Never log or output credentials in plain text
- Don't include master.key in version control
- Use different credentials for each environment
- Rotate credentials regularly
- Limit access to credential files on production servers
Credential Rotation & Management
# Rotating credentials
# 1. Generate new master key
rails credentials:edit --environment production
# 2. Create backup of current credentials
cp config/credentials/production.yml.enc config/credentials/production.yml.enc.backup
# 3. Update credentials with new values
rails credentials:edit --environment production
# 4. Deploy with new master key
# 5. Test thoroughly
# 6. Remove backup after successful deployment
# Credential validation
# config/initializers/credential_validator.rb
class CredentialValidator
def self.validate!
required_credentials = [
'secret_key_base',
'database.password',
'api_keys.stripe.secret'
]
required_credentials.each do |path|
value = path.split('.').reduce(Rails.application.credentials) { |creds, key|
creds&.send(key.to_sym)
}
if value.nil? || value.empty?
raise "Missing required credential: #{path}"
end
end
end
end
# In config/application.rb
if Rails.env.production?
CredentialValidator.validate!
end
# Credential monitoring
# lib/tasks/credentials.rake
namespace :credentials do
desc "Check credential health"
task health_check: :environment do
credentials = Rails.application.credentials
checks = {
'Secret Key Base' => credentials.secret_key_base.present?,
'Database Password' => credentials.database&.password.present?,
'Redis URL' => credentials.redis&.url.present?,
'Stripe Keys' => credentials.api_keys&.stripe&.secret.present?
}
checks.each do |name, status|
puts "#{name}: #{status ? '✅' : '❌'}"
end
end
desc "List all credential keys (without values)"
task list: :environment do
def print_keys(obj, prefix = '')
case obj
when ActiveSupport::EncryptedConfiguration
obj.config.each { |key, value| print_keys(value, "#{prefix}#{key}.") }
when Hash
obj.each { |key, value| print_keys(value, "#{prefix}#{key}.") }
else
puts "#{prefix.chomp('.')}"
end
end
print_keys(Rails.application.credentials)
end
end