Membedah rate limiting dan proteksi DoS di Rails: Rack::Attack untuk throttling login & API, perlindungan brute-force, dan pertahanan berlapis di level aplikasi

Setelah di episode 19 kalian mengelola kerentanan dependency, episode 20 membahas ancaman yang tidak memerlukan exploit sama sekali: serangan volume — brute-force login, spam API, dan DoS (Denial of Service). Semua bisa dihentikan oleh satu mekanisme sederhana: rate limiting.
Mengapa episode ini penting? Karena endpoint login tanpa throttle adalah undangan brute-force, dan endpoint publik tanpa limit bisa di-banjiri hingga server kolaps. Rate limiting adalah pertahanan murah dengan dampak besar — dan Rails menawarkan Rack::Attack, middleware yang bisa dikonfigurasi dalam hitungan menit.
Bayangkan endpoint login: penyerang bisa mencoba jutaan kombinasi password per jam. Tanpa limit, hanya masalah waktu sebelum password lemah tertembus. Rate limiting membatasi jumlah request dari satu sumber dalam periode waktu tertentu:
Response 429 memberi tahu client (dan penyerang) bahwa mereka melampaui batas. Untuk bot, 429 plus jeda adalah sinyal pindah target; untuk user normal, batas yang wajar tidak akan pernah tersentuh.
bundle add rack-attack
bin/rails g rack_attack:installGenerator membuat config/initializers/rack_attack.rb. Aktifkan middleware di aplikasi:
config.middleware.use Rack::AttackPola paling penting: batasi percobaan login per IP:
Rack::Attack.throttle("logins/ip", limit: 5, period: 1.minute) do |req|
req.post? && req.path == "/login" && req.ip
endMaksimal 5 percobaan login per menit per IP. Percobaan ke-6 mendapat 429. Untuk pencegahan brute-force yang lebih pintar, kombinasikan dengan pembatasan per email — tetapi hati-hati: membatasi per email bisa menjadi alat untuk lockout attack (penyerang mengunci korban dengan sengaja). Kombinasi IP + email dengan limit lebih longgar di email adalah praktik umum.
Untuk API, batasi berdasarkan token autentikasi, bukan IP (IP bisa shared di belakang NAT):
Rack::Attack.throttle("api/token", limit: 300, period: 1.minute) do |req|
req.headers["Authorization"] if req.path.start_with?("/api/v1/")
end300 request per menit per token — batas awal yang masuk akal untuk API publik, disesuaikan dengan kebutuhan.
Rack::Attack mendukung tiga kategori kontrol:
Rack::Attack.safelist("allow-healthcheck") do |req|
req.path == "/healthcheck"
end
Rack::Attack.blocklist("block-known-bad-ip") do |req|
req.ip == "203.0.113.66"
endRack::Attack memberi blocklist/throttle default response 403/429 dengan body polos. Sesuaikan agar client paham:
Rack::Attack.throttled_responder = lambda do |req|
headers = {
"Retry-After" => "60",
"Content-Type" => "application/json"
}
[429, headers, [{ error: { code: "rate_limited", message: "Terlalu banyak request, coba lagi dalam 60 detik" } }.to_json]]
endHeader Retry-After memberi tahu client kapan boleh mencoba lagi — penting untuk integrasi API yang baik.
Note
Rack::Attack menyimpan counter di Rack middleware state secara default — artinya counter tidak di-share antar instance. Untuk production multi-instance, aktifkan Redis store: Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new. Tanpa ini, throttling bisa di-bypass dengan membagi request ke beberapa server.
Rate limiting di aplikasi adalah satu lapisan dari pertahanan total melawan DoS. Urutan lapisan dari luar ke dalam:
| Lapisan | Tool | Melindungi Dari |
|---|---|---|
| CDN/WAF | Cloudflare, AWS WAF | Volumetric attacks, DDoS besar |
| Reverse proxy | Nginx/Caddy | Connection limits, bandwidth |
| Rack::Attack | Aplikasi | Brute-force, API abuse |
| Database pool | pool: config | Connection exhaustion |
| App scaling | Auto-scaling (episode 24) | Kapasitas membludak |
Rack::Attack tidak bisa menghentikan DDoS 100 Gbps — itu tugas CDN/WAF. Yang bisa ia lakukan: menghentikan serangan aplikasi-level (brute-force, spam, abuse) yang paling sering terjadi dan paling murah dilakukan penyerang.
Konfigurasi lengkap yang masuk akal untuk app blog:
Rack::Attack.throttle("req/ip", limit: 300, period: 5.minutes) do |req|
req.ip if req.path == "/"
end
Rack::Attack.throttle("logins/ip", limit: 5, period: 1.minute) do |req|
req.post? && req.path == "/login" && req.ip
end
Rack::Attack.throttle("comments/ip", limit: 10, period: 1.minute) do |req|
req.post? && req.path =~ %r{\A/posts/\d+/comments} && req.ip
endRetry-After — client bingung kapan boleh coba lagi.Episode 20 membekali kalian rate limiting Rails: Rack::Attack sebagai middleware yang bisa dikonfigurasi, throttling login per IP untuk anti brute-force, throttling API per token, safelist/blocklist, response 429 yang informatif, serta model pertahanan berlapis melawan DoS.
Inti yang harus dibawa pulang:
throttle, safelist, blocklist dalam satu middleware.Di episode 21 selanjutnya kita akan membedah advanced Active Record — query optimization, select/group/aggregations, raw SQL, composite PK (Rails 8.1), dan praktik reporting query dengan indexing. Sampai jumpa di episode 21!