Rails 8.0 adds rate limiting to ActionController
Rate limiting is a technique used to control the rate of requests or actions a user, device, or application can make within a specific timeframe. It's like having a traffic light for your online resources, ensuring everyone gets a fair chance and preventing congestion.
It's the practice of putting a cap on the number of requests a user or device can make to your app within a specific timeframe. This helps prevent:
-
Denial-of-service attacks (DoS): Malicious actors flooding your app with requests, overwhelming its resources and crashing it.
-
Resource abuse: Heavy users consuming disproportionate resources, impacting legitimate users and app performance.
-
Brute-force attacks: Hackers attempting to guess passwords or exploit vulnerabilities through repeated attempts.
Before Rails 8.0
No built-in method was available to implement rate limiting on the number of requests.
One approach to implement rate limiting was to use the rack-attack gem.
How to use Rack::Attack in Rails to implement rate-limiting
Add rack-attack to Gemfile
The first step is to add the rack-attack
to Gemfile
and
execute the bundle install
command.
gem "rack-attack"
Add it to your Rails middleware stack
In your config/application.rb
,
add the below line inside the Application
class.
config.middleware.use Rack::Attack
Configure rate-limiting rules
To add the rate-limiting rules,
create a file named rack_attack.rb
inside the
config/initializers
directory
and
add your rules.
A small example of adding a rate-limiting of 10 requests per minute from a particular ID address is as follows:
# config/initializers/rack_attack.rb
Rack::Attack.throttle('requests by ip', limit: 10, period: 1.minute) do |req|
req.ip
end
If you want to set a rate-limiting for the number of login attempts, you can add a custom code as below:
Rack::Attack.throttle('login attempt', limit: 3, period: 1.minute) do |req|
req.params['email'].presence if req.path == '/login' && req.post?
end
The rack-attack
gem is helpful for rate limiting,
but for a growing application,
this approach is ideal.
You might have to add a lot of custom code for the endpoints you need to
add rate limiting.
You would need to navigate to rack_attack.rb
file every time to check which
actions in your controller are rate limited.
In Rails 8.0
Rails 8.0 adds rate limiting to the Action Controller.
You need to set the rate_limit
function in your controller.
The rate_limit
function accepts the following parameters:
-
to: The maximum number of requests allowed, beyond which the rate limiting error will be raised.
-
within: The maximum number of requests allowed in a given time window.
For example,
to set a rate limit of 3 sign-up attempts within 1 minute,
you need to add the below code.
Let's say your Rails application has a LoginController
,
you can explicitly set the rate limit for the new
action.
class LoginController < ApplicationController
rate_limit to: 3, within: 1.minute, only: :new
def new
...
end
end
You can set the rate limit to specific actions
in the controller by passing the only
or
except
option.
by: and with: optional parameters
By default,
rate limits are based on IP addresses,
but you can pass your custom function using the by:
option.
If you want to rate limit the action by request domain instead of IP,
you can pass the by:
option.
class LoginController < ApplicationController
rate_limit to: 3, within: 1.minute, by: -> { request.domain },
only: :new
def new
...
end
end
To prevent overload,
requests that surpass the rate limit are turned away with a 429 Too Many Requests
error.
You have the flexibility to create unique responses for these situations by
supplying a callable object using the with:
parameter.
class LoginController < ApplicationController
rate_limit to: 3, within: 1.minute,
with: -> { redirect_to home_path, alert: "3 Login Attempts failed. Please try after 12 hours" },
only: :new
def new
...
end
end
Note:
The rate limiting implementation leverages a Redis server
with Kredis
1.7.0+ for storage.
The Kredis limiter type,
incorporating a failsafe mechanism,
guarantees action execution proceeds even in Redis inaccessibility scenarios.
If your application has Kredis
below 1.7.0
version,
it will raise the below error:
Rate limiting requires Kredis 1.7.0+. Please update by calling `bundle update kredis`.
If the application has no Kredis the error below gets raised.
Rate limiting requires Redis and Kredis. Please ensure you have Redis installed on your system and the Kredis gem in your Gemfile.
To know more about this feature, please refer to this PR.
You can refer the below video for more details.