Web Application Security in Ruby on Rails: A Comprehensive Guide 2024
Table of contents
- Why Web Application Security Matters
- Rails' Built-in Security Features
- Implementing Authentication
- Authorization - Controlling Access
- Secure File Uploads
- Security Tools for Rails in 2024
- 1. Brakeman - Static Analysis Security Scanner
- 2. Bundle Audit - Dependency Security Scanner
- 3. Rack::Attack - Request Throttling and Blocking
- 4. Dawn Scanner - Configuration Analysis
- 5. RuboCop - Code Quality and Security Linter
- 6. Secure Headers - HTTP Header Security
- 7. Devise Security - Enhanced Authentication Security
- 8. Active Record Doctor - Database Security Analysis
- Security Best Practices
- Security Monitoring
- Bonus: New Security Tool for Rails 7+ Applications
- Conclusion
- Frequently Asked Questions (FAQs)
- 1. How often should I update my Rails application's dependencies?
- 2. What's the best way to handle user passwords in Rails?
- 3. How can I protect my Rails API from rate limiting and abuse?
- 4. What security headers should I enable in my Rails application?
- 5. How do I implement secure file uploads in Rails?
- 6. What are the best practices for session management in Rails?
Web application security is the cornerstone of protecting your Rails applications from malicious attacks and unauthorized access.
Think of it as building a fortress around your application – you need strong walls (authentication), guards at the gate (authorization), security cameras (monitoring), and a well-trained security team (security tools and practices).
Why Web Application Security Matters
Every day, web applications process sensitive information like credit card details, personal information, and business data.
Without proper security measures, this data is vulnerable to various attacks:
SQL Injection - Attackers trying to manipulate your database queries
Cross-Site Scripting (XSS) - Malicious scripts injected into your pages
Cross-Site Request Forgery (CSRF) - Unauthorized commands from trusted users
Session Hijacking - Stealing user sessions to impersonate them
Man-in-the-Middle Attacks - Intercepting data in transit
Denial of Service (DoS) - Overwhelming your servers
Security Misconfigurations - Vulnerabilities from improper setup
Rails' Built-in Security Features
Ruby on Rails comes with several security features out of the box.
Let's explore each one and understand how to use them effectively.
1. CSRF Protection
Cross-Site Request Forgery protection prevents attackers from tricking your users into executing unwanted actions.
Rails automatically includes CSRF tokens in your forms.
# This is automatically included in ApplicationController
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception
protect_from_forgery with: :exception
# You can also use :null_session instead of :exception
# protect_from_forgery with: :null_session
end
# In your views, Rails automatically adds CSRF tokens to forms
<%= form_for @user do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
2. SQL Injection Prevention
SQL injection is one of the most common attack vectors.
Rails' Active Record provides safe methods to prevent SQL injection:
class UsersController < ApplicationController
def search
# BAD - Vulnerable to SQL injection
# User.where("name = '#{params[:name]}'")
# GOOD - Safe parameterized query
@users = User.where(name: params[:name])
# GOOD - If you need complex queries
@users = User.where("name LIKE ?", "%#{params[:name]}%")
# GOOD - Multiple conditions
@users = User.where(
"created_at >= ? AND role = ?",
params[:start_date],
params[:role]
)
end
end
3. XSS Protection
Cross-Site Scripting attacks inject malicious scripts into your pages.
Rails provides several helpers to prevent XSS:
# In your views
<% # BAD - Vulnerable to XSS %>
<%= @user.profile_html %>
<% # GOOD - Escapes HTML %>
<%= sanitize @user.profile_html %>
<% # GOOD - Rails automatically escapes html by default %>
<%= @user.name %>
<% # GOOD - Using safe HTML with specific tags %>
<%= sanitize @user.bio, tags: %w(p br strong em), attributes: %w(id class) %>
Implementing Authentication
Authentication verifies user identity.
While Rails pre-8 versions don't include authentication by default, Devise is the most popular solution:
# Gemfile
gem 'devise'
# Generate devise configuration
# $ rails generate devise:install
# app/models/user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:lockable, :timeoutable, :trackable
# Add custom fields
validates :username, presence: true, uniqueness: true
# Add security features
def lock_access!
self.failed_attempts = 0
super
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
end
end
Authorization - Controlling Access
Authorization determines what authenticated users can do.
Pundit provides an elegant way to handle authorization:
# Gemfile
gem 'pundit'
# app/policies/application_policy.rb
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
end
# app/policies/article_policy.rb
class ArticlePolicy < ApplicationPolicy
def update?
user.admin? || record.author == user
end
def destroy?
user.admin? || record.author == user
end
end
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
def update
authorize @article
if @article.update(article_params)
redirect_to @article, notice: 'Updated successfully'
else
render :edit
end
end
private
def set_article
@article = Article.find(params[:id])
end
end
Secure File Uploads
File uploads can be dangerous if not handled properly.
Here's how to implement secure file uploads:
# app/models/document.rb
class Document < ApplicationRecord
has_one_attached :file
# Define allowed content types
ALLOWED_TYPES = %w[
application/pdf
application/msword
image/jpeg
image/png
].freeze
# Validations
validate :acceptable_file
private
def acceptable_file
return unless file.attached?
unless file.byte_size <= 10.megabyte
errors.add(:file, "is too large (max 10MB)")
end
unless ALLOWED_TYPES.include?(file.content_type)
errors.add(:file, "must be a PDF, Word, JPEG, or PNG")
end
end
end
# app/controllers/documents_controller.rb
class DocumentsController < ApplicationController
def create
@document = current_user.documents.build(document_params)
if @document.save
process_file_in_background(@document)
redirect_to @document, notice: 'File uploaded successfully'
else
render :new
end
end
private
def document_params
params.require(:document).permit(:file)
end
def process_file_in_background(document)
ProcessDocumentJob.perform_later(document)
end
end
Security Tools for Rails in 2024
Let's dive deep into the essential security tools available for Ruby on Rails applications. Each tool serves a specific purpose in your security arsenal.
1. Brakeman - Static Analysis Security Scanner
Brakeman is your first line of defense in security testing. It performs static analysis of your Rails application code to identify potential security vulnerabilities before they reach production.
# Add to Gemfile
group :development, :test do
gem 'brakeman', require: false
end
# Create a custom configuration file (.brakeman.yml)
:skip_files:
- 'vendor/**/*'
- 'node_modules/**/*'
:ignore_model_output: false
:exit_on_warn: true
:output_files:
- 'tmp/brakeman.html'
- 'tmp/brakeman.json'
Key Features:
Identifies SQL injection risks
Detects XSS vulnerabilities
Finds unprotected mass assignments
Checks for session setting issues
Scans for unsafe redirects
Identifies unsafe rendering
Example usage and output:
# Basic scan
$ brakeman
# Scan specific files or directories
$ brakeman app/controllers app/models
# Generate a detailed HTML report
$ brakeman -o brakeman-report.html
# Example output:
# >> Security Warnings:
# >> Cross-Site Scripting: ... [High]
# >> SQL Injection: ... [High]
# >> Mass Assignment: ... [Medium]
2. Bundle Audit - Dependency Security Scanner
Bundle Audit helps you identify known security vulnerabilities in your application's dependencies by checking against the Ruby Advisory Database.
# Add to Gemfile
group :development, :test do
gem 'bundler-audit'
end
# Create a rake task (lib/tasks/security.rake)
namespace :security do
desc 'Run bundle-audit'
task :audit do
require 'bundler/audit/cli'
Bundler::Audit::CLI.start(['check', '--update'])
end
desc 'Update vulnerability database'
task :update do
require 'bundler/audit/cli'
Bundler::Audit::CLI.start(['update'])
end
end
Key Features:
Scans Gemfile.lock for vulnerable versions
Checks for insecure gem sources
Provides detailed vulnerability information
Regular database updates
Example usage:
# Update vulnerability database
$ bundle audit update
# Check for vulnerabilities
$ bundle audit check
# Example output:
# Name: actionpack
# Version: 6.0.0
# Advisory: CVE-2020-8164
# Criticality: High
# URL: https://groups.google.com/forum/#!topic/rubyonrails-security/f6ioe4sdpbY
# Title: Possible Strong Parameters Bypass in ActionPack
# Solution: upgrade to ~> 6.0.3.1
3. Rack::Attack - Request Throttling and Blocking
Rack::Attack is a rack middleware for blocking and throttling abusive requests. It's essential for protecting your application from DDoS attacks and brute force attempts.
# Add to Gemfile
gem 'rack-attack'
# config/initializers/rack_attack.rb
class Rack::Attack
### Configure Cache ###
Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"])
### Throttle Spammy Clients ###
throttle('req/ip', limit: 300, period: 5.minutes) do |req|
req.ip unless req.path.start_with?('/assets')
end
### Prevent Brute-Force Login Attacks ###
throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
if req.path == '/users/sign_in' && req.post?
req.ip
end
end
### Block Suspicious User Agents ###
blocklist('block suspicious UA') do |req|
req.user_agent =~ /^BadBot/ ||
req.user_agent =~ /^BlackWidow/ ||
req.user_agent =~ /^Bot\ mailto:craftbot@yahoo.com/ ||
req.user_agent =~ /^ChinaClaw/ ||
req.user_agent =~ /^Custo/ ||
req.user_agent =~ /^DISCo/ ||
req.user_agent =~ /^Download\ Demon/ ||
req.user_agent =~ /^eCatch/ ||
req.user_agent =~ /^EirGrabber/
end
### Custom Throttle Response ###
self.throttled_response = lambda do |env|
[ 429, # status
{'Content-Type' => 'application/json'}, # headers
[{error: "Too many requests. Please try again later."}.to_json] # body
]
end
end
Key Features:
Request rate limiting
IP-based blocking
User agent blocking
Custom throttling rules
Flexible response configuration
4. Dawn Scanner - Configuration Analysis
Dawn Scanner examines your Rails application's configuration and code for security issues, including OWASP Top 10 vulnerabilities.
# Add to Gemfile
group :development, :test do
gem 'dawnscanner'
end
# Create a custom rake task (lib/tasks/security.rake)
namespace :security do
desc 'Run Dawn security checks'
task scan: :environment do
system('dawn --html -F results.html .') # Generate HTML report
system('dawn --console -F yaml .') # Console output
end
end
Key Features:
Analyzes security configurations
Detects OWASP Top 10 vulnerabilities
Reviews Rails security best practices
Validates authentication setups
Examines session configurations
Checks cookie settings
Identifies database security issues
Provides detailed HTML reports
5. RuboCop - Code Quality and Security Linter
While primarily known as a style checker, RuboCop includes security-related checks through its Security department.
# Add to Gemfile
group :development, :test do
gem 'rubocop'
gem 'rubocop-rails'
end
# Create .rubocop.yml configuration
require:
- rubocop-rails
AllCops:
NewCops: enable
# Security-specific configurations
Security/Eval:
Enabled: true
Security/JSONLoad:
Enabled: true
Security/MarshalLoad:
Enabled: true
Security/Open:
Enabled: true
Key Features:
Identifies unsafe method usage
Checks for secure defaults
Validates proper encoding
Ensures safe deserialization
6. Secure Headers - HTTP Header Security
The Secure Headers gem helps you set security-related HTTP headers properly.
# Add to Gemfile
gem 'secure_headers'
# config/initializers/secure_headers.rb
SecureHeaders::Configuration.default do |config|
config.x_frame_options = "DENY"
config.x_content_type_options = "nosniff"
config.x_xss_protection = "1; mode=block"
config.x_download_options = "noopen"
config.x_permitted_cross_domain_policies = "none"
config.referrer_policy = %w(origin-when-cross-origin strict-origin-when-cross-origin)
# Content Security Policy
config.csp = {
default_src: %w('none'),
script_src: %w('self' 'unsafe-inline'),
style_src: %w('self' 'unsafe-inline'),
img_src: %w('self' data:),
connect_src: %w('self'),
font_src: %w('self'),
base_uri: %w('self'),
form_action: %w('self'),
frame_ancestors: %w('none'),
block_all_mixed_content: true,
upgrade_insecure_requests: true
}
end
Key Features:
Sets security headers automatically
Configurable CSP policies
HSTS support
XSS protection headers
Clickjacking prevention
7. Devise Security - Enhanced Authentication Security
An extension to Devise that adds additional security features.
# Add to Gemfile
gem 'devise'
gem 'devise-security'
# Enable security extensions in user model
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:password_expirable, :password_archivable,
:session_limitable, :security_questionable
# Password strength validation
validate :password_complexity
private
def password_complexity
return if password.nil?
unless password.match(/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/)
errors.add :password, 'must include at least one uppercase letter, one lowercase letter, one number, and one special character'
end
end
end
Key Features:
Password expiration
Password history
Session limitations
Security questions
Strong password validation
8. Active Record Doctor - Database Security Analysis
This tool helps identify potential security issues in your database configuration and ActiveRecord usage.
# Add to Gemfile
group :development, :test do
gem 'active_record_doctor'
end
# Run analysis
$ rake active_record_doctor:analyze
# Example output:
# Unindexed Foreign Keys:
# - comments.post_id
# Missing Foreign Key Constraints:
# - comments.user_id
# Extraneous Indexes:
# - index_users_on_email (can be replaced by unique_index_users_on_email)
Key Features:
Identifies missing indexes
Finds redundant indexes
Checks foreign key constraints
Validates database configuration
Security Best Practices
Keep Dependencies Updated
# Regular updates
$ bundle update --conservative
# Check for vulnerabilities
$ bundle audit
Secure Configuration
# config/environments/production.rb
Rails.application.configure do
# Force SSL
config.force_ssl = true
# Secure cookies
config.session_store :cookie_store,
key: '_app_session',
secure: true,
httponly: true,
same_site: :strict
# Content Security Policy
config.content_security_policy do |policy|
policy.default_src :self
policy.font_src :self, :https, :data
policy.img_src :self, :https, :data
policy.object_src :none
policy.script_src :self
policy.style_src :self, :https
end
end
Environment Variables
# Use dotenv for development
# Gemfile
gem 'dotenv-rails', groups: [:development, :test]
# .env
DATABASE_URL=postgresql://localhost/myapp
SECRET_KEY_BASE=your-secret-key
AWS_ACCESS_KEY=your-access-key
# config/initializers/secret_token.rb
MyApp::Application.config.secret_key_base = ENV['SECRET_KEY_BASE']
Security Monitoring
Implement logging and monitoring to detect security issues:
# config/initializers/lograge.rb
Rails.application.configure do
config.lograge.enabled = true
config.lograge.custom_options = lambda do |event|
{
user_id: event.payload[:user_id],
ip: event.payload[:ip],
params: event.payload[:params],
exception: event.payload[:exception]&.first
}
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def append_info_to_payload(payload)
super
payload[:user_id] = current_user&.id
payload[:ip] = request.remote_ip
end
end
[Previous sections remain the same...]
Bonus: New Security Tool for Rails 7+ Applications
Rails 7 and 8 introduce several new security tools and features that enhance application security. Here is a one notable addition:
ActiveRecord::Encryption
A new built-in encryption system for protecting sensitive database attributes without additional gems.
# app/models/user.rb
class User < ApplicationRecord
encrypts :social_security_number, deterministic: true
encrypts :personal_notes
encrypts :credit_card_number, deterministic: true, downcase: true
# Custom encryption key per attribute
encrypts :health_data, key: :health_encryption_key
private
def health_encryption_key
# Custom key derivation logic
@health_key ||= Rails.application.credentials.health_data_key
end
end
# config/credentials.yml.enc
active_record_encryption:
primary_key: <%= SecureRandom.alphanumeric(32) %>
deterministic_key: <%= SecureRandom.alphanumeric(32) %>
key_derivation_salt: <%= SecureRandom.alphanumeric(32) %>
Key Features:
Built-in attribute encryption
Deterministic and non-deterministic encryption
Key rotation support
Transparent encryption/decryption
Search capability on encrypted fields
Conclusion
Security is not a one-time setup but a continuous process.
Regular security audits, keeping dependencies updated, and following security best practices are essential.
The tools and practices covered in this guide provide a solid foundation for securing your Rails applications.
Remember:
Use Rails' built-in security features
Implement authentication and authorization properly
Validate and sanitize all user input
Keep your dependencies updated
Monitor your application for security issues
Regular security audits
Use security tools and frameworks
Security is everyone's responsibility on the development team.
Make it a part of your development culture, not an afterthought.
Frequently Asked Questions (FAQs)
1. How often should I update my Rails application's dependencies?
Answer: You should review and update dependencies at least monthly. However, security-related updates should be applied immediately. Set up automated security alerts using:
# Gemfile
gem 'bundler-audit'
gem 'ruby_audit'
# Add to your deployment pipeline
namespace :security do
desc 'Run security audits'
task :audit do
sh 'bundle-audit check --update'
sh 'ruby-audit check'
end
end
2. What's the best way to handle user passwords in Rails?
Answer: Use has_secure_password with bcrypt and enforce strong password policies:
class User < ApplicationRecord
has_secure_password
# Password validation
validate :password_complexity
private
def password_complexity
return if password.blank?
requirements = {
digit: /\d/,
uppercase: /[A-Z]/,
lowercase: /[a-z]/,
special: /[^A-Za-z0-9]/
}
requirements.each do |type, regex|
unless password.match?(regex)
errors.add(:password, "must include at least one #{type}")
end
end
end
end
3. How can I protect my Rails API from rate limiting and abuse?
Answer: Implement rate limiting and API authentication:
# config/initializers/rack_attack.rb
class Rack::Attack
# Rate limiting by IP
throttle('api/ip', limit: 100, period: 1.minute) do |req|
req.ip if req.path.start_with?('/api/')
end
# Rate limiting by API key
throttle('api/key', limit: 1000, period: 1.hour) do |req|
req.headers['X-API-Key']
end
end
# app/controllers/api/base_controller.rb
module Api
class BaseController < ApplicationController
before_action :verify_api_key
private
def verify_api_key
api_key = request.headers['X-API-Key']
@api_client = ApiClient.find_by_active_key(api_key)
head :unauthorized unless @api_client
end
end
end
4. What security headers should I enable in my Rails application?
Answer: Enable these essential security headers:
# config/initializers/security_headers.rb
Rails.application.config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
'X-XSS-Protection' => '1; mode=block',
'X-Content-Type-Options' => 'nosniff',
'X-Download-Options' => 'noopen',
'X-Permitted-Cross-Domain-Policies' => 'none',
'Referrer-Policy' => 'strict-origin-when-cross-origin',
'Content-Security-Policy' => [
"default-src 'self'",
"img-src 'self' data: https:",
"style-src 'self' 'unsafe-inline'",
"script-src 'self'",
"connect-src 'self'",
"frame-ancestors 'none'"
].join('; ')
}
5. How do I implement secure file uploads in Rails?
Answer: Implement proper validation, virus scanning, and storage:
class Document < ApplicationRecord
has_one_attached :file
validates :file,
content_type: %w[application/pdf application/msword],
size: { less_than: 10.megabytes }
after_create :scan_for_viruses
private
def scan_for_viruses
temp_file = file.download
scan_result = VirusScanner.scan(temp_file)
if scan_result.infected?
file.purge
raise SecurityError, "Infected file detected"
end
ensure
temp_file.close
temp_file.unlink
end
end
6. What are the best practices for session management in Rails?
Answer: Implement secure session handling:
# config/initializers/session_store.rb
Rails.application.config.session_store :redis_store,
servers: [ENV['REDIS_URL']],
expire_after: 4.hours,
key: '_secure_session_id',
secure: Rails.env.production?,
httponly: true,
same_site: :strict
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :verify_session
after_action :update_session_timestamp
private
def verify_session
if session[:created_at].present? && session[:created_at] < 12.hours.ago
reset_session
redirect_to new_session_path
end
end
def update_session_timestamp
session[:last_activity] = Time.current
end
end
Subscribe to my newsletter
Read articles from Chetan Mittal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Chetan Mittal
Chetan Mittal
I stumbled upon Ruby on Rails beta version in 2005 and has been using it since then. I have also trained multiple Rails developers all over the globe. Currently, providing consulting and advising companies on how to upgrade, secure, optimize, monitor, modernize, and scale their Rails apps.