Ultimate Rails Security Guide: Best Practices for Ruby on Rails Applications in 2025
The digital ecosystem is becoming increasingly vulnerable to cyberattacks.
With the proliferation of web applications, security risks such as data breaches, injection attacks, and exploitation of misconfigured systems are escalating.
Rails, as a robust web application framework, has maintained its reputation for being "secure by default."
However, securing a Rails application requires more than relying on its built-in features—it necessitates proactive strategies, continuous learning, and adherence to best practices.
The latest releases of Ruby 3.3.6 and Rails 8 introduce cutting-edge enhancements that provide developers with tools to build applications that are both functional and secure.
These updates emphasize encryption, improved session management, and query sanitization, addressing modern threats comprehensively.
This guide equips developers with strategies to harness Rails' security features effectively.
1. Common Vulnerabilities
Understanding vulnerabilities that Rails applications may face is a critical first step in securing them.
1.1 Command Injection
Command injection occurs when user input is executed as part of a system-level command. It can compromise the entire server hosting the application.
Why It Happens
Methods like system
, exec
, or backticks (``) allow direct interaction with the operating system shell, making them inherently risky if user inputs are not sanitized.
Examples and Best Practices
A typical insecure implementation:
# Vulnerable code
system("ping #{params[:target]}")
If params[:target]
is unvalidated, an attacker could inject a command like ; rm -rf /
to delete server files.
To secure this:
Use safer syntax: The array format prevents shell metacharacters from being interpreted:
system("ping", params[:target])
Validate Inputs: Allow only safe and expected values:
valid_targets = %w[localhost 127.0.0.1] raise ArgumentError, "Invalid target" unless valid_targets.include?(params[:target])
1.2 SQL Injection
SQL injection exploits vulnerabilities in database query formation, allowing attackers to manipulate SQL statements.
Rails Protections
Rails mitigates SQL injection by default through parameterized queries and Active Record's query interface.
An insecure example:
# Vulnerable code
User.where("email = '#{params[:email]}'")
A secure implementation:
User.where(email: params[:email])
Rails 8 introduces sanitize_sql_like
, a new method for escaping special characters in inputs for LIKE
queries:
search_term = "#{params[:search]}%"
User.where("name LIKE ?", User.sanitize_sql_like(search_term))
Additional Best Practices
Use Prepared Statements: Always prefer Rails' query methods like
find_by
orwhere
.Avoid Raw SQL: If raw SQL is necessary, sanitize inputs thoroughly.
1.3 Cross-Site Scripting (XSS)
XSS allows attackers to inject malicious scripts into webpages that users trust, potentially stealing session tokens or other sensitive information.
Rails' Built-In XSS Protection
By default, Rails escapes all user inputs in views, preventing most XSS attacks.
However, carelessness with methods like html_safe
can lead to vulnerabilities:
# Vulnerable code
<%= user_input.html_safe %>
To safely allow some HTML, use the sanitize
helper:
<%= sanitize user_input, tags: %w(strong em a), attributes: %w(href) %>
Content Security Policy (CSP)
Implement a CSP to restrict script sources:
Rails.application.config.content_security_policy do |policy|
policy.script_src :self, :https
end
1.4 Cross-Site Request Forgery (CSRF)
CSRF tricks users into performing actions they didn’t intend, using their authentication status against them.
Rails CSRF Protection
Rails includes CSRF protection by default, embedding authenticity tokens into forms:
<%= form_with model: @user do |f| %>
<%= f.text_field :name %>
<% end %>
Best Practices
Enable
protect_from_forgery
in controllers:protect_from_forgery with: :exception
For APIs, use secure headers to authenticate requests instead of CSRF tokens.
1.5 Mass Assignment
Mass assignment vulnerabilities occur when untrusted user inputs modify sensitive attributes.
Strong Parameters
Rails uses Strong Parameters to control which attributes can be mass-assigned:
def user_params
params.require(:user).permit(:name, :email)
end
2. Framework Security Features
Rails offers a rich set of security tools to minimize vulnerabilities. However, using these tools effectively requires a solid understanding of their configuration and application.
2.1 HTTP Security Headers
Rails’ built-in support for HTTP security headers makes it easier to protect applications from browser-based attacks like clickjacking, MIME-type sniffing, and mixed content. Here's a deeper look:
Strict-Transport-Security (HSTS):
Forces browsers to use HTTPS and avoid insecure HTTP requests.
Example configuration:config.force_ssl = true
Content Security Policy (CSP):
Restricts the sources of scripts, styles, and other assets, reducing the risk of XSS.
Example:config.content_security_policy do |policy| policy.default_src :self policy.script_src :self, :https policy.style_src :self, :https end
X-Frame-Options and X-XSS-Protection:
These headers mitigate clickjacking and XSS attacks by default in Rails.
2.2 Logging Sensitive Data
Rails offers middleware to filter sensitive data, such as passwords or tokens, from logs. Misconfigured logging can expose private information.
Example:
config.filter_parameters += [:password, :credit_card_number]
2.3 Handling File Uploads Securely
Improperly handled file uploads can lead to vulnerabilities such as unrestricted file uploads or directory traversal. Use gems like CarrierWave
or ActiveStorage
and validate file types explicitly:
validates :avatar, content_type: ['image/png', 'image/jpg', 'image/jpeg']
3. Advanced Topics
Beyond the basics, advanced security techniques strengthen your application against evolving threats.
3.1 Managing Credentials and Secrets
Rails’ encrypted credentials system simplifies secret management. However, it's essential to keep encrypted files out of source control for enhanced security.
Environment-Specific Credentials:
Separate production and development credentials to avoid exposing sensitive keys in non-production environments.Rotate Keys Regularly:
Rotate and revoke secrets periodically to minimize the risk of exposure.
3.2 Role-Based Access Control (RBAC)
Gems like Pundit
and CanCanCan
make it easier to implement role-based authorization, ensuring users can only access resources appropriate for their roles.
Example with Pundit
:
class PostPolicy < ApplicationPolicy
def update?
user.admin? || record.author == user
end
end
3.3 Secure APIs
When building APIs, use the following security practices:
Token-Based Authentication:
PreferBearer tokens
or OAuth for stateless authentication.Rate Limiting:
Implement rate limits to prevent abuse using middleware likeRack::Attack
.CORS Configuration:
Restrict cross-origin requests to trusted domains usingrack-cors
.
3.4 Authentication Security Best Practices
Authentication is a critical security component that requires careful implementation and ongoing maintenance.
Password Security
Implement strong password policies using Rails' built-in validations:
class User < ApplicationRecord
has_secure_password
validates :password,
length: { minimum: 12 },
format: {
with: /\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/,
message: "must include uppercase, lowercase, number and special character"
}
end
This code:
Enforces minimum password length of 12 characters
Requires mix of uppercase, lowercase, numbers, and special characters
Uses Rails' has_secure_password for secure password hashing
Session Management
Configure secure session settings to prevent session-based attacks:
# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store,
key: '_app_session',
expire_after: 12.hours,
secure: Rails.env.production?,
same_site: :strict,
httponly: true
This configuration:
Sets session expiration to 12 hours
Enforces secure flag in production
Implements SameSite strict policy
Enables HttpOnly flag to prevent XSS attacks
Multi-Factor Authentication (MFA)
Implement two-factor authentication using devise-two-factor:
class User < ApplicationRecord
devise :two_factor_authenticatable,
:two_factor_backupable
def generate_two_factor_code
self.otp_secret = User.generate_otp_secret
self.otp_code = ROTP::TOTP.new(otp_secret).now
save
end
end
This implementation:
Adds two-factor authentication capability
Includes backup codes for account recovery
Uses time-based one-time passwords (TOTP)
Account Lockout
Protect against brute force attacks with account lockout:
class User < ApplicationRecord
def failed_attempt
self.failed_attempts += 1
if failed_attempts > 5
self.locked_at = Time.current
end
save
end
end
This mechanism:
Tracks failed login attempts
Locks account after 5 failed attempts
Requires manual unlock or time-based automatic unlock
3.5 Enhanced API Security
JWT Implementation
Implement secure JWT handling for API authentication:
class JsonWebToken
SECRET_KEY = Rails.application.credentials.secret_key_base
def self.encode(payload, exp = 24.hours.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, SECRET_KEY)
end
def self.decode(token)
decoded = JWT.decode(token, SECRET_KEY)[0]
HashWithIndifferentAccess.new decoded
rescue JWT::ExpiredSignature
nil
end
end
This JWT implementation:
Uses Rails' secret key base for token signing
Includes token expiration
Handles expired tokens gracefully
Rate Limiting
Protect your API from abuse with rate limiting:
# config/initializers/rack_attack.rb
class Rack::Attack
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
# Limit all API requests to 300 per 5 minutes
throttle('req/ip', limit: 300, period: 5.minutes) do |req|
req.ip if req.path.start_with?('/api/')
end
# Stricter limits for authentication endpoints
throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
req.ip if req.path == '/api/login' && req.post?
end
end
This configuration:
Sets general API rate limits
Implements stricter limits for sensitive endpoints
Uses IP-based throttling
3.6 WebSocket Security
When using Action Cable, implement proper authentication and authorization:
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
verified_user = User.find_by(id: cookies.signed['user.id'])
verified_user || reject_unauthorized_connection
end
end
end
This setup:
Authenticates WebSocket connections
Ties connections to specific users
Rejects unauthorized connections
3.7 Secure File Upload
Implement secure file uploads with ActiveStorage:
class Document < ApplicationRecord
has_one_attached :file
validate :acceptable_file
private
def acceptable_file
return unless file.attached?
unless file.byte_size <= 10.megabyte
errors.add(:file, "is too large (maximum is 10MB)")
end
acceptable_types = ["application/pdf", "image/jpeg", "image/png"]
unless acceptable_types.include?(file.content_type)
errors.add(:file, "must be a PDF, JPEG or PNG")
end
end
end
This implementation:
Validates file size limits
Restricts file types
Prevents malicious file uploads
3.8 Database Encryption
Implement encryption for sensitive data:
class User < ApplicationRecord
encrypts :social_security_number, deterministic: true
encrypts :medical_notes
encrypts :bank_account_number, downcase: true
# Key rotation
def rotate_encryption_key!
self.class.encrypt_attributes.each do |attribute|
clear_attribute_changes([attribute])
send("#{attribute}_will_change!")
end
save!
end
end
This setup:
Encrypts sensitive fields
Supports deterministic encryption where needed
Includes key rotation capability
3.9 Security Headers Configuration
# 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',
'Permissions-Policy' => 'accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()'
}
Rails.application.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, :https
policy.style_src :self, :https
policy.connect_src :self, :https
policy.base_uri :self
policy.frame_ancestors :none
policy.form_action :self
policy.require_trusted_types_for 'script'
end
This comprehensive header configuration:
Prevents clickjacking attacks
Enables XSS protection
Prevents MIME type sniffing
Controls browser feature permissions
3.10 Privacy Compliance
Implement privacy-related features for compliance with regulations like GDPR and CCPA:
class User < ApplicationRecord
def data_export
{
personal_info: {
email: email,
name: name,
created_at: created_at
},
activity_logs: activity_logs.as_json,
preferences: user_preferences.as_json
}.to_json
end
def forget_me!
update(
email: "redacted_#{SecureRandom.hex(6)}@deleted",
name: "Redacted User",
deleted_at: Time.current
)
activity_logs.destroy_all
end
end
This implementation:
Provides data export functionality
Implements right to be forgotten
Maintains privacy audit trail
4. Testing and Auditing
Regular testing and auditing are essential for identifying and addressing vulnerabilities.
4.1 Automated Security Testing
Security testing tools integrate into CI/CD pipelines to identify vulnerabilities during development:
Brakeman: A static analysis tool for Rails applications that detects common security issues.
Bundler-Audit: Checks for known vulnerabilities in gem dependencies.
Example CI configuration:
jobs:
security:
steps:
- uses: actions/checkout@v2
- run: brakeman -A
- run: bundle audit check --update
4.2 Manual Code Reviews
In addition to automated scans, regular peer code reviews can identify security risks that automated tools might miss.
4.3 Dependency Management
Use tools like dependabot
to ensure all dependencies remain up to date with the latest security patches.
5. Staying Updated
Staying informed about security threats and updates is crucial for maintaining application security.
5.1 Monitoring and Alerts
Leverage monitoring tools like New Relic, Sentry, or Honeybadger to track anomalies in real-time.
We at Bestweb Ventures use Honeybadger for monitoring our own and client’s MVPs as it gives us the needed monitoring features in its free version.
5.2 Learning Resources
Keep up with Rails and security best practices:
Newsletters like Ruby Weekly.
5.3 Contribution to Security
Contributing to open-source security projects, reporting vulnerabilities, or participating in bug bounty programs helps strengthen the ecosystem.
6. Conclusion
Security in Rails applications is not just about fixing bugs—it’s about creating a culture of vigilance and continuous improvement.
Here’s how to achieve it:
Embrace Rails’ Built-In Security Features: Rails provides strong defaults for protecting against common vulnerabilities. Ensure they are enabled and configured correctly.
Implement Robust Access Control: Use role-based access control and strong parameter validation to prevent unauthorized access.
Regularly Audit and Test: Adopt automated and manual testing to detect vulnerabilities early.
Stay Proactive: Keep your dependencies, Rails version, and libraries updated. Monitor security trends and participate in the broader developer community.
Educate and Empower Your Team: Security is a shared responsibility. Train your team to recognize and address potential risks proactively.
By integrating these practices into your development workflow, you not only protect your users but also enhance the resilience and reputation of your application.
Rails, combined with the right strategies, offers the foundation for building secure, high-quality web applications.
Subscribe to my newsletter
Read articles from BestWeb Ventures directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
BestWeb Ventures
BestWeb Ventures
From cutting-edge web and app development, using Ruby on Rails, since 2005 to data-driven digital marketing strategies, we build, operate, and scale your MVP (Minimum Viable Product) for sustainable growth in today's competitive landscape.