Rails 8.0 built-in Rate Limiting

Introduction

Hi everyone, I’m @Sam from the Money Forward ID Service Development team.

In our project, which is built with Rails, we’ve been using the third-party gem rack-attack to mitigate certain types of attacks. However, with the release of Rails 8.0, a new rate limiting feature has been introduced. This native solution has allowed us to eliminate our dependency on rack-attack. So, in this article, I’d like to talk to you about this new feature. It’s really easy to use.

A Brief Overview of rack-attack

rack-attack has been the go-to solution for rate limiting in Rails applications for years. It provides capabilities to prevent abuse, such as brute force login attempts or API overuse. With its flexibility, rack-attack allows developers to implement complex rate-limiting strategies, such as per-user, per-IP, or even custom rules using middleware.

However, rack-attack is a third-party gem. While powerful, it introduces additional dependency management, requires configuration, and may lead to maintenance challenges as your project develops.

Rails 8.0 Built-in Rate Limiting

Rails 8.0 introduces rate limiting as part of ActionController::RateLimiting. This feature eliminates the need for external libraries by providing a streamlined and Rails native way to manage request throttling. It integrates directly into Rails controllers and works seamlessly with the framework’s caching mechanisms.

Key features:

  • Requiring no external dependencies.
  • Configurable rules using intuitive parameters.
  • Flexible identification options, such as IP addresses or custom attributes.
  • Customizable responses for throttled requests.
  • Supports multiple rate-limiting strategies within a single controller.

How It Works

Rails built-in Rate Limiting uses the application’s caching layer to track request counts and enforce limits. Here are examples of how you can implement it.

Throttling Login Attempts

Limit users to 10 login attempts per 3 minutes based on their IP address as default:

class SessionsController < ApplicationController
  rate_limit to: 10, within: 3.minutes, only: :create
end

When the limit is reached, Rails will automatically respond with 429 Too Many Requests if there is no specific action defined for rate limiting.

Customizing Throttling Rules

You can create a rule that limits requests based on domains or other custom attributes:

class SignupsController < ApplicationController
  rate_limit to: 50, within: 1.minute, 
    by: -> { request.domain }, 
    with: -> { redirect_to root_path, alert: "Too many signups from this domain!" }, 
    only: :new
end

When the limit is reached, Rails will redirect users to the root path with a message.

Using a Dedicated Cache Store

Rate limiting relies on a backing ActiveSupport::Cache store and defaults to the global config.cache_store.

If you don’t want to store rate limits in the same datastore, you can pass a custom store in the store parameter.

class APIController < ApplicationController
  RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"])
  
  rate_limit to: 100, within: 1.minute, store: RATE_LIMIT_STORE
end

Implementing Multiple Limits

Apply different rate-limiting rules for short-term and long-term abuse prevention via the name: parameter:

class SessionsController < ApplicationController
  rate_limit to: 5, within: 1.second, name: "short-term", only: :create
  rate_limit to: 50, within: 1.day, name: "long-term", only: :create
end

Challenges in Replacing rack-attack with Rails built-in Rate Limiting

One significant challenge when transitioning from rack-attack to Rails built-in Rate Limiting is feature parity. For example, rack-attack allows us to block requests from specific IP addresses, a capability not currently supported by Rails rate-limiting feature. To address this gap, we had to explore alternative solutions, such using Cloudflare which we already use to handle IP-based blocking.

To better understand the differences, here’s a comparison table highlighting the key aspects of rack-attack and Rails built-in Rate Limiting feature:

Aspect,Rails built-in Rate Limiting,rack-attack
Integration,"Native to Rails, reducing external dependencies and ensuring seamless updates.","Third-party gem that requires additional dependency management."
Ease of Use,"Simple configuration with intuitive Rails-like syntax.","More complex setup and configuration, especially for advanced use cases."
Algorithms Supported,"Limited to basic cache-counter algorithm (e.g., requests per time window).","Supports advanced algorithms like exponential backoff, token bucket, and leaky bucket."
Middleware Support,"Operates only at the controller level; lacks global request filtering capability.","Middleware-level throttling that applies across all requests, regardless of controller logic."
Extensibility,"Limited extensibility; does not yet support advanced rate-limiting strategies.","Fully extensible with customizable rules and support for complex strategies."
AspectRails built-in Rate Limitingrack-attack
IntegrationNative to Rails, reducing external dependencies and ensuring seamless updates.Third-party gem that requires additional dependency management.
Ease of UseSimple configuration with intuitive Rails-like syntax.More complex setup and configuration, especially for advanced use cases.
Algorithms SupportedLimited to basic cache-counter algorithm (e.g., requests per time window).Supports advanced algorithms like exponential backoff, token bucket, and leaky bucket.
Middleware SupportOperates only at the controller level; lacks global request filtering capability.Middleware-level throttling that applies across all requests, regardless of controller logic.
ExtensibilityLimited extensibility; does not yet support advanced rate-limiting strategies.Fully extensible with customizable rules and support for complex strategies.

Conclusion

Currently, Rails built-in Rate Limiting is quite basic. I’m hopeful it will be enhanced in the future. So, if your project relies on advanced algorithms provided by rack-attack for blocking requests, replacing rack-attack with Rails built-in Rate Limiting might be difficult.

Published-date