Secure Your Rails App – Security Best Practices

FOUNDATION

1. Start with secure defaults and environment hygiene

Rails ships with sensible security defaults, but they only work if you keep them enabled and avoid leaking secrets. Treat configuration as code and environments as isolated blast zones.

Use credentials and environment variables correctly

Never hard‑code secrets (API keys, database passwords, tokens) in your codebase. Use rails credentials:edit or environment variables managed by your deployment platform.

# config/database.yml
production:
  adapter: postgresql
  url: <%= ENV.fetch("DATABASE_URL") %>
  pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>
Checklist: Secrets are not committed to Git, credentials are encrypted, and each environment (dev, staging, prod) has its own keys.

Keep Rails and dependencies up to date

Many real‑world breaches come from known vulnerabilities in old gems. Use tools like bundle audit or GitHub Dependabot and schedule regular dependency reviews.

# Example: run regularly in CI
bundle exec bundle-audit check --update
AUTH & ACCESS

2. Harden authentication and authorization

Authentication proves who a user is; authorization decides what they can do. Both must be explicit and consistent across your app.

Use proven authentication libraries

Instead of rolling your own login system, use mature gems like Devise or Sorcery. They handle password hashing, account locking, and session management with battle‑tested defaults.

# Example Devise configuration (config/initializers/devise.rb)
config.pepper = ENV["DEVISE_PEPPER"]
config.stretches = Rails.env.test? ? 1 : 12
config.timeout_in = 30.minutes

Enforce strong password and session policies

  • Require minimum length and complexity for passwords.
  • Use has_secure_password or Devise’s bcrypt/argon2 support.
  • Enable session expiration and re‑authentication for sensitive actions.

Centralize authorization logic

Use Pundit or CanCanCan to keep authorization rules out of controllers and views. This reduces the risk of “forgot to check” vulnerabilities.

# Example Pundit policy
class ProjectPolicy < ApplicationPolicy
  def update?
    user.admin? || record.owner_id == user.id
  end
end
Risk pattern: Checking permissions in views only (e.g., hiding buttons) without enforcing them in controllers or policies. Attackers can still hit the endpoints directly.
INPUT & OUTPUT

3. Defend against injection and XSS

Any time user input touches a query, command, or HTML, you should assume it’s hostile. Rails gives you tools to neutralize that input—use them consistently.

Use parameterized queries and Active Record

Avoid string interpolation in SQL. Use Active Record or parameterized queries to prevent SQL injection.

# Safe
User.where(email: params[:email])

# Unsafe
User.where("email = '#{params[:email]}'")

Escape output by default

Rails ERB escapes output automatically. Only use raw or .html_safe when you are absolutely sure the content is safe.

<!-- Safe -->
<%= @comment.body %>

<!-- Dangerous if @comment.body contains user input -->
<%= raw @comment.body %>

Validate and sanitize rich text

For user‑generated HTML (e.g., WYSIWYG editors), use sanitization:

# Example sanitization
sanitized = ActionController::Base.helpers.sanitize(
  params[:content],
  tags: %w[p b i strong em a ul ol li],
  attributes: %w[href]
)
CSRF & STATE

4. Protect against CSRF and session attacks

Cross‑Site Request Forgery (CSRF) abuses a user’s authenticated browser to perform unwanted actions. Rails has built‑in CSRF protection—don’t disable it casually.

Keep CSRF protection enabled

In ApplicationController, ensure CSRF protection is turned on:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
end

Rails automatically embeds authenticity tokens in forms and verifies them on submission. If you expose JSON APIs, use protect_from_forgery with appropriate strategies or separate API controllers using token‑based auth.

Harden cookies and sessions

  • Set secure and httponly flags on cookies.
  • Use same_site: :lax or :strict where possible.
  • Rotate session secrets if compromised and invalidate old sessions.
# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store,
  key: "_myapp_session",
  secure: Rails.env.production?,
  httponly: true,
  same_site: :lax
TRANSPORT & HEADERS

5. Enforce HTTPS and security headers

Transport security ensures data isn’t exposed in transit. Browser security headers add an extra layer of defense against common attacks.

Force HTTPS everywhere

Use config.force_ssl = true in production to redirect HTTP to HTTPS and set secure cookies.

# config/environments/production.rb
config.force_ssl = true

Configure Content Security Policy (CSP)

CSP limits where scripts, styles, and other resources can be loaded from, significantly reducing XSS impact.

# config/initializers/content_security_policy.rb
Rails.application.configure do
  config.content_security_policy do |policy|
    policy.default_src :self
    policy.script_src  :self, :https
    policy.style_src   :self, :https, :unsafe_inline
    policy.img_src     :self, :https, :data
  end
end

Set additional security headers

Use middleware or your reverse proxy (Nginx, Apache, CDN) to set headers like:

  • Strict-Transport-Security (HSTS)
  • X-Frame-Options (clickjacking protection)
  • X-Content-Type-Options: nosniff
DATA & FILES

6. Protect data at rest and handle file uploads safely

Sensitive data and user uploads are high‑value targets. Treat them as untrusted and compartmentalize aggressively.

Encrypt sensitive attributes

For fields like tokens, personal identifiers, or secrets, use Rails’ built‑in encryption or gems like attr_encrypted (for older versions).

# Rails 7+ Active Record encryption
class User < ApplicationRecord
  encrypts :ssn, :personal_token
end

Validate and scan file uploads

  • Use Active Storage or Shrine with strict content‑type validation.
  • Store uploads on S3 or similar, not on the app server’s public directory.
  • Serve files via signed URLs and avoid executing uploaded content.
# Example validation
class Document < ApplicationRecord
  has_one_attached :file

  validate :acceptable_file

  def acceptable_file
    return unless file.attached?

    unless file.byte_size <= 10.megabytes
      errors.add :file, "is too big"
    end

    acceptable_types = ["application/pdf"]
    unless acceptable_types.include?(file.content_type)
      errors.add :file, "must be a PDF"
    end
  end
end
MONITORING

7. Log, monitor, and respond

Security is not a one‑time configuration; it’s an ongoing feedback loop. You need visibility into what your app is doing and how it’s being used.

Log security‑relevant events

  • Log authentication failures and suspicious login patterns.
  • Log permission denials and unexpected parameter usage.
  • Redact sensitive fields (passwords, tokens, secrets) from logs.
# config/initializers/filter_parameter_logging.rb
Rails.application.config.filter_parameters += %i[
  password password_confirmation token api_key
]

Set up alerts and rate limiting

Use tools like Rack::Attack to throttle abusive requests and integrate your logs with monitoring platforms (e.g., Datadog, Sentry, Logstash) for anomaly detection.

# Example Rack::Attack throttle
Rack::Attack.throttle("logins/ip", limit: 10, period: 60.seconds) do |req|
  req.ip if req.path == "/users/sign_in" && req.post?
end
Goal: You should be able to answer “What happened?” quickly after a suspicious event, without exposing sensitive data in the process.
CULTURE

8. Make security part of your development workflow

The most secure Rails apps are built by teams that treat security as a habit, not a feature. Bake it into your process.

Integrate security into CI/CD

  • Run static analysis and dependency checks on every merge.
  • Fail builds on critical vulnerabilities.
  • Automate basic checks (e.g., secret scanning, linting for unsafe patterns).

Review code with a security lens

During code review, explicitly ask: “What happens if this input is malicious?” and “What if this endpoint is called by someone who shouldn’t have access?”

Document and train

Maintain a short, living “Rails Security Checklist” for your team and revisit it regularly. New developers should learn your security expectations as part of onboarding.

Bottom line: Security is a continuous practice. Rails gives you powerful tools—your job is to turn them into habits.