HIGH brute force attackrailsfirestore

Brute Force Attack in Rails with Firestore

Brute Force Attack in Rails with Firestore — how this specific combination creates or exposes the vulnerability

A brute force attack against a Ruby on Rails application that uses Google Cloud Firestore typically targets authentication or password reset endpoints. Because Firestore is often used as a user store or a session/state store, Rails controllers that validate credentials against Firestore can become the effective gate for account access.

Without proper rate controls, each login attempt can map to a Firestore document read (e.g., querying by email). An attacker can iterate over many usernames or passwords, generating high read operations that may be observable via logs or monitoring. While Firestore enforces its own quotas, the application layer must enforce rate limits to prevent rapid, unthrottled requests that constitute a brute force attempt.

Common insecure patterns in Rails include using User.where(email: params[:email]).first directly against the Firestore-backed model without any exponential backoff or captcha, and returning distinct error messages (e.g., “email not found” vs “incorrect password”), which enables username enumeration. The Rails session store or token verification logic that reads from Firestore may also be abused if tokens or API keys are guessable or leaked, effectively turning Firestore into a persistence mechanism for attack state.

Because Firestore queries are predictable and index-backed, attackers can probe for valid emails or use automated scripts to attempt many combinations. If the Rails app does not enforce per-identity or per-IP rate limits, the unauthenticated attack surface expands. This risk is amplified when Firestore rules allow public read or when indices inadvertently expose user collections.

middleBrick’s 12 security checks include Rate Limiting and Authentication, which can detect missing or weak controls around login endpoints that rely on Firestore. The scanner tests unauthenticated attack surfaces and can identify whether brute force protections such as account lockout, delays, or captchas are absent, providing prioritized findings with severity and remediation guidance.

Firestore-Specific Remediation in Rails — concrete code fixes

Implementing robust brute force defenses in Rails with Firestore requires both application-level controls and secure usage of the Firestore client. Below are concrete, realistic examples.

1. Rate-limited sign-in with constant-time response

Use a cache-based rate limiter (e.g., Redis or ActiveSupport::Cache) to enforce per-email and per-IP limits, and return a generic, consistent message to avoid enumeration.

# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  RATE_LIMIT = 5   # attempts
  WINDOW      = 5.minutes

  def create
    email = params.dig(:user, :email)&.strip&.downcase
    ip    = request.remote_ip

    cache_key_email = "brute:email:#{email}"
    cache_key_ip    = "brute:ip:#{ip}"

    if increment_and_check(cache_key_email, RATE_LIMIT, WINDOW) ||
       increment_and_check(cache_key_ip,    RATE_LIMIT, WINDOW)
      # Always perform the same work and delay to prevent timing leaks
      User.dummy_verify(email, params[:password])
      render json: { error: 'Invalid credentials' }, status: :unauthorized
      return
    end

    user = User.find_by_email(email)
    if user&.authenticate(params[:password])
      reset_access_attempts(cache_key_email, cache_key_ip)
      session[:user_id] = user.id
      render json: { status: 'ok' }, status: :ok
    else
      # Constant-time dummy verification to obscure timing
      User.dummy_verify(email, params[:password])
      render json: { error: 'Invalid credentials' }, status: :unauthorized
    end
  end

  private

  def increment_and_check(key, limit, window)
    count = $cache.read(key, raw: true) || 0
    if count >= limit
      true
    else
      $cache.write(key, count + 1, expires_in: window, raw: true)
      false
    end
  end

  def reset_access_attempts(email_key, ip_key)
    $cache.delete(email_key)
    $cache.delete(ip_key)
  end
end

2. Secure Firestore client usage in the User model

Use the Firestore Ruby client safely by avoiding public rules and parameterizing queries. Do not expose Firestore errors that reveal existence of an email.

# app/models/user.rb
class User < ApplicationRecord
  # Assume Firestore is initialized via a service object or initializer
  def self.firestore
    @firestore ||= Google::Cloud::Firestore.new
  end

  def self.find_by_email(email)
    doc_ref = firestore.doc("users/#{email}")
    # Use a secure document read; ensure rules require authentication for sensitive reads
    doc = doc_ref.get
    doc && doc.exists? ? from_firestore_doc(doc) : nil
  end

  def self.from_firestore_doc(doc)
    attrs = doc.data
    new(
      id:       doc.document_id,
      email:    attrs["email"],
      password: attrs["password_hash"]
    )
  end

  def authenticate(password_attempt)
    # Use a constant-time comparison where feasible
    return false unless password_hash
    ActiveSupport::SecurityUtils.secure_compare(password_hash, BCrypt::Password.create(password_attempt, cost: Rails.application.config.bcrypt_cost))
  end

  def self.dummy_verify(email, password_attempt)
    # Always run a dummy hash operation to obscure timing differences
    BCrypt::Password.create("dummy_salt_#{email}") if password_attempt.present?
  end
end

3. Firestore security rules to enforce least privilege

Ensure rules do not allow public enumeration. For example, disallow list operations and require authentication for user-specific reads/writes.


rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{email} {
      allow read, write: if request.auth != null && request.auth.token.email == email;
      deny list: if true;
    }
  }
}

4. MiddleBrick integrations for continuous protection

With the Pro plan, you can enable continuous monitoring so that any regression in rate limiting or authentication configuration is caught early. Add the middleBrick GitHub Action to fail builds if the security score drops below your chosen threshold, or use the CLI to scan from the terminal: middlebrick scan <url>. The MCP Server also lets you scan APIs directly from your AI coding assistant within the Rails project.

Frequently Asked Questions

Why does the sign-in response always return the same error message?
To prevent username enumeration, Rails should return a generic message like “Invalid credentials” regardless of whether the email exists. This denies attackers useful signals during brute force probing.
Can Firestore rules alone stop brute force attacks?
Firestore rules can restrict who can read or write documents, but they do not provide built-in rate limiting. You must implement application-level rate limits and delays in Rails to mitigate brute force attempts effectively.