Coba perhatikan endpoint POST /login kita — sejak awal tidak ada batasan berapa kali seseorang boleh mencoba login dalam rentang waktu tertentu. Ini celah klasik untuk serangan brute-force: seseorang bisa mencoba ribuan kombinasi password secara otomatis dalam hitungan detik. Di part ini kita akan menambahkan rate limiting untuk mencegah hal itu, sekaligus melindungi API kita secara umum dari pemakaian berlebihan.
RateLimiter di Goravel
Goravel sudah menyiapkan tempat khusus untuk konfigurasi rate limiting: method configureRateLimiting() di app/providers/route_service_provider.go — sebelumnya masih kosong, sekarang kita isi.
func (receiver *RouteServiceProvider) configureRateLimiting() {
loginLimit := 5
if v, ok := facades.Config().Env("LOGIN_RATE_LIMIT").(string); ok && v != "" {
if parsed, err := strconv.Atoi(v); err == nil {
loginLimit = parsed
}
}
facades.RateLimiter().For("login", func(ctx contractshttp.Context) contractshttp.Limit {
return limit.PerMinute(loginLimit).By(ctx.Request().Ip())
})
facades.RateLimiter().For("api", func(ctx contractshttp.Context) contractshttp.Limit {
return limit.PerMinute(60).By(ctx.Request().Ip())
})
}
Di sini kita mendaftarkan dua limiter dengan nama berbeda:
login— maksimal 5 request per menit secara default, cocok untuk endpoint sensitif seperti login/register yang rawan brute-force.api— maksimal 60 request per menit, sebagai batas umum untuk semua endpoint supaya API tidak dibanjiri request dari satu sumber.
.By(ctx.Request().Ip()) menentukan basis pembatasannya — di sini per alamat IP. Kalau mau, key ini bisa diganti jadi per user (misalnya helpers.AuthUserID(ctx)) untuk endpoint yang butuh autentikasi.
Angka 5 untuk limiter login sengaja dibuat bisa dioverride lewat environment variable LOGIN_RATE_LIMIT, bukan di-hardcode. Perhatikan juga kita pakai facades.Config().Env(...), bukan os.Getenv(...) biasa — nilai di file .env dibaca Goravel ke dalam config internalnya sendiri, tidak ikut disuntikkan ke environment variable asli milik proses. Jadi os.Getenv("LOGIN_RATE_LIMIT") akan selalu kosong walau sudah diisi di .env; yang benar adalah lewat facade config ini.
Kenapa Limit Ini Perlu Dibuat Configurable
Begitu limiter login ini aktif dengan nilai default 5/menit, feature test yang kita tulis di Part 17 langsung gagal! Penyebabnya: HTTP test client bawaan Goravel selalu memakai IP palsu yang sama (192.0.2.1) untuk semua request, dan dalam satu kali go test kita memanggil /register dan /login berkali-kali dalam hitungan detik lewat beberapa test case sekaligus — jauh melebihi 5 request/menit. Request keenam dan seterusnya langsung kena 429, bukan status yang diharapkan test (200, 409, dst).
Ini contoh nyata kenapa rate limiting perlu dipikirkan bersamaan dengan automated testing. Solusinya, kita set LOGIN_RATE_LIMIT ke nilai yang jauh lebih longgar di .env lokal:
LOGIN_RATE_LIMIT=1000
Supaya di lingkungan development/testing kita bebas menjalankan test berkali-kali, sementara di production .env tetap kosongkan variabel ini (atau set ke 5) supaya proteksi brute-force yang sesungguhnya tetap ketat.
Menerapkan Limiter "api" secara Global
Semua request otomatis kena limiter api lewat global middleware:
facades.Route().GlobalMiddleware(append(http.Kernel{}.Middleware(), frameworkmiddleware.Throttle("api"))...)
Menerapkan Limiter "login" ke Endpoint Auth
Bungkus route /register dan /login dengan limiter yang lebih ketat, di routes/api.go:
facades.Route().Middleware(frameworkmiddleware.Throttle("login")).Group(func(router route.Router) {
router.Post("/register", userController.Register)
router.Post("/login", userController.Login)
})
Karena kedua limiter (api dan login) sama-sama aktif di endpoint /login, request ke sana akan dicek terhadap keduanya — mana yang lebih dulu tercapai batasnya, itu yang berlaku.
Testing
Jalankan server, lalu kirim request POST /login berkali-kali dengan cepat (misalnya kredensial salah supaya tidak perlu data asli):
for i in 1 2 3 4 5 6 7; do
curl -s -o /dev/null -w "Request $i -> %{http_code}\n" \
-X POST http://127.0.0.1:3000/login \
-H "Content-Type: application/json" \
-d '{"email":"nonexistent@example.com","password":"wrongpass"}'
done
Lima request pertama akan tetap direspon normal (401 Unauthorized karena kredensialnya memang salah), tapi mulai request ke-6 dalam satu menit yang sama, responnya berubah jadi 429 Too Many Requests — limiter login (5/menit) sudah kena batasnya.
Coba juga endpoint biasa seperti GET /posts, perhatikan header response-nya:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 52
Header ini membantu client API tahu berapa sisa jatah request mereka sebelum kena limit.
Penutup
Rate limiting ini jadi lapisan pertahanan tambahan yang murah tapi efektif — bukan pengganti autentikasi/otorisasi yang sudah kita bangun di part-part sebelumnya, tapi pelengkap yang membuat API lebih tahan dari penyalahgunaan. Di part berikutnya kita akan membahas cara mendokumentasikan API ini supaya lebih mudah dipakai oleh developer lain.
Bagian dari Series: Seri Tutorial Belajar Framework Goravel Rest API untuk Pemula
Overview Seri ini membahas cara membangun REST API menggunakan Goravel, framework web berbasis Golang yang terinspirasi dari Laravel. Materi mencakup...
Lihat Series Lengkap