Rails 8 Adds Rate Limiting to Action Controller via Kredis Limiter Type

Let’s understand what is Rate Limiting

Rate limiting is a technique used to control the rate of incoming requests or traffic to a server, API, or service. It helps in limiting the rate at which requests are processed which ensures system security and performance.

By restricting the rate of requests made by individual clients or IP addresses, helps in preventing abuse, such as denial-of-service attacks or brute-force login attempts.

Now question is how to do this in Rails.

Before Rails 8.0

Before Rails 8 there were different ways to implement rate limiting that depends on specific requirements and constraints. One such way of it is by using rack-attack gem.

To use this gem, we need to create a new file config/initializers/rack_attack.rb and then we can write rate limiting rules in this file.

For example, if we want to block access to all admins we can define rules in the following way:

# config/initializers/rack_attack.rb

Rack::Attack.blocklist("block all access to admin") do |request|
  # Requests are blocked if the return value is truthy
  request.path.start_with?("/admin")
end

In the above example request object is Rack::Request.

Another example is if we want to limit the rate of requests from certain IP, we can do it like this:

# config/initializers/rack_attack.rb

Rack::Attack.throttle("requests by ip", limit: 3, period: 2) do |request|
  request.ip
end

Let’s see what is happening here, The Rack::Attack.throttle method is used to define rate limiting rules.

requests by ip: This is the name of the rate-limiting rule. It serves as a unique identifier for the rule and can be used to reference it elsewhere in the configuration.

limit: This parameter specifies the maximum number of requests allowed within the defined period. In this case, the limit is set to 3 requests.

period: This parameter specifies the duration of the rate-limiting period in seconds. In this case, the period is set to 2 seconds.

request.ip: This line inside the block extracts the IP address of the incoming request. The rate limiting rule is applied per IP address, meaning each unique IP address is subject to its rate limit.

Now in Rails 8 we have an inbuilt API to handle Rate Limiting rules

Now, Rails 8 has the rate_limit method which can help us define rate limiting rules directly in the Action Controller. It removes the dependency of using external gems.

The rate_limit method takes the following parameters.

to: The maximum number of allowed requests.

within: Time constraints within the maximum number of requests are allowed.

only: Controller Actions on which we want to apply rate limiting rules.

except: Controller Actions on which rate limiting rules will not apply.

Let’s take a look at how we can define it. In the below example, we are allowing 10 requests within 3 minutes to only create action.

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

Here, Rate limits are by default unique to the ip address making the request, and requests that exceed the rate limit are refused with a 429 Too Many Requests response.

But what if we would like our custom logic instead of applying these rules just to IP address, and also if we want to modify our response?

For that we get two additional parameters that we can use:

by: It takes a function that can be used to define our custom rate_limit logic.

with: It helps us modify our custom response.

Let’s see the example below.

class LoginController < ApplicationController
  rate_limit to: 3, within: 1.minute,
    by: -> { request.domain },
    with: -> { redirect_to home_path, alert: "Too many login attemps on domain!, Please try after some time" },
    only: :new
end

In the above example, we are defining rate limit rules to requests coming from specific domains and returning a modified 429 Too Many Requests response.

Note:

For the rate limit feature to work, application should have a Redis server and Kredis 1.7.0+ available in the bundle.

Kredis (Keyed Redis)

Kredis provides abstractions or wrappers for organizing data in Redis using a single key. In Redis, keys are used to store and retrieve data, and Kredis enhances this by grouping related data structures and types under a single key.

It uses the Kredis limiter type underneath so in case Redis is inaccessible, the rate limit will not refuse action execution.

A limiter is a specialized version of a counter. While both counters and limiters can track the number of times an event or action occurs, a limiter adds additional functionality. Specifically, a limiter allows you to check whether a predefined limit has been exceeded, and it provides a fail-safe mechanism.

To know more about this feature, please go through this pull request.

Need help on your Ruby on Rails or React project?

Join Our Newsletter