Rails 8 introduces a built-in rate limiting API

Yedhin Kizhakkethara

Yedhin Kizhakkethara

February 13, 2024

Rails 8 introduces a built-in rate limiting API

In the dynamic world of web development, managing the flow of requests is crucial for maintaining a responsive and reliable application. Rate limiting is a powerful technique that acts as the traffic cop for your API, ensuring fair access to resources and preventing potential chaos. In a nutshell, rate limiting is the practice of controlling the rate of requests a user, device, or application can make within a set timeframe.

In this blog post, we'll delve into the concept of rate limiting, exploring its significance and implementation in Rails 8.0.

The Need for Rate Limiting

It's crucial in ensuring the following:

  • Security Shield: Guards against Denial-of-Service (DoS) attacks, thwarting malicious attempts to flood your app and crash it.
  • Resource Balance: Prevents resource abuse by capping heavy users, ensuring fair resource distribution and maintaining optimal app performance.
  • Brute-Force Defender: Stalls hackers attempting password guesses or exploiting vulnerabilities through relentless, repeated attempts.

How Rack::Attack Paved the Way

In the pre-Rails 8.0 era, the go-to solution for rate limiting was the rack-attack gem. This gem allowed developers to set up rate-limiting rules by adding custom code in the rack_attack.rb file. While effective, this approach required manual intervention for each endpoint and could become cumbersome in a growing application. This external dependency brought its own set of challenges, such as the need for custom code and constant vigilance over rate-limited endpoints.

Rails 8.0: Native Rate Limiting

Rails 8.0 brings a native rate-limiting feature to the Action Controller, streamlining the process and eliminating the need for external gems. Developers can now set rate limits directly within their controllers using the rate_limit method.

Let's take a look at the usage:

Define Limits

Utilize the rate_limit method within your controller actions, specifying the maximum allowed requests and the corresponding timeframe. The rate_limit method accepts the following parameters:

  • to: The maximum number of requests allowed, beyond which the rate limiting error will be raised, that is with a 429 Too Many Requests response.
  • within: The maximum number of requests allowed in a given time window.
  • only: Which all controller actions ought to be rate limited.
  • except: Which all controller actions to be omitted from rate limiting.

An example:

class SignupController < ApplicationController
  rate_limit to: 4, within: 1.minute, only: :create

  def create
    # ...
  end
end

Granular Control

Target specific actions or employ custom logic for nuanced control. For instance, you can limit requests based on domain instead of IP address.

rate_limit to: 4, within: 1.minute, by: -> { request.domain }, only: :create

Tailored Responses

By default, rate-limited requests receive a 429 Too Many Requests error. However, you can personalize the response for a more informative user experience.

rate_limit to: 4, within: 1.minute, with: -> { redirect_to(ip_restrictions_controller_url), alert: "Signup Attempts failed four times. Please try again later." }, only: :create

Custom Cache Stores

The Rails 8.0 rate-limiting implementation at the moment allows us to make use a wide range of backends as its store.

It includes:

  • Memcached
  • Redis (including alternative Redis clients like Dalli)
  • Database-backed stores
  • File-based stores

The Rate Limiting API seamlessly integrates with Rails' caching mechanisms, leveraging ActiveSupport::Cache stores. Developers can specify custom cache stores if they require separate handling for rate limits compared to other cache data. This integration ensures efficient storage and retrieval of rate limit data, optimizing performance. An example:

class SignupController < ApplicationController
  RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"])
  rate_limit to: 8, within: 2.minutes, store: RATE_LIMIT_STORE
end

What to look forward to

At the moment the built-in rate limiting feature is limited in its extensibility. There are cases where this feature cannot be used as a direct replacement for logic that's based on rack-attack gem. A good example is a scenario where we might want to use an exponential backoff based rate limiting algorithm, which can be implemented using rack-attack gem but not with the built-in Rails feature at the moment.

Right now we are constrained to stick on to the default cache counter algorithm that Rails comes shipped with. In future we can expect a more generic limiter interface, allowing users to explore advanced rate-limiting algorithms such as exponential backoff or leaky bucket or token bucket etc. This change would open the door for developers to swap the implementation based on their specific requirements.

Please check out the following pull requests for more details:

If this blog was helpful, check out our full blog archive.

Stay up to date with our blogs.

Subscribe to receive email notifications for new blog posts.