Melindungi API publik dari abuse dan brute-force dengan throttling DRF: throttle classes bawaan (per-user, per-IP), scope kustom, kombinasi multiple throttles, serta strategi rate limit untuk endpoint yang mahal.

Setelah hardening transport dan headers di episode 18, satu ancaman masih terbuka: abuse — scraping tak terbatas, brute-force login, atau spam ke endpoint yang mahal. Di episode ini kita memasang rate limiting & throttling: membatasi seberapa sering satu client bisa memanggil API dalam rentang waktu.
Mengapa topik ini penting? Karena tanpa throttling, satu aktor bisa menghabiskan resource kalian — CPU, query database, dan bandwidth — dengan sejuta request gratis. Rate limit bukan sekadar pengaman: ia juga pelindung bisnis, karena menutup penyalahgunaan yang bisa menjatuhkan layanan.
DRF menyediakan throttle_classes per view. Tiga implementasi bawaan:
AnonRateThrottle — batas untuk user anonim (rate key anon).UserRateThrottle — batas untuk user login (rate key user).ScopedRateThrottle — batas berbasis scope yang dipasang per-view.REST_FRAMEWORK = {
"DEFAULT_THROTTLE_RATES": {
"anon": "100/day",
"user": "1000/hour",
"burst": "20/min",
"sustained": "200/hour",
}
}Format rate: <jumlah>/<periode> dengan periode second, minute, hour, day. Terapkan di view:
from rest_framework.throttling import AnonRateThrottle, ScopedRateThrottle
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.filter(status="published")
serializer_class = PostSerializer
throttle_classes = [AnonRateThrottle, ScopedRateThrottle]
throttle_scope = "sustained"
@action(detail=False, methods=["post"], throttle_scope="burst")
def search(self, request):
...AnonRateThrottle selalu dipasang untuk membatasi anonim; ScopedRateThrottle memakai throttle_scope di level viewset dan bisa di-override per action. Endpoint search yang mahal mendapat scope burst yang lebih ketat.
UserRateThrottle dan AnonRateThrottle berbeda cara mengidentifikasi client:
UserRateThrottle memakai request.user — satu batas per akun, tidak peduli dari IP mana.AnonRateThrottle memakai IP address (REMOTE_ADDR), karena anonim tidak punya identitas.Mengapa perlu keduanya? Karena user yang sama bisa datang dari banyak IP (mobile, kantor), dan banyak anonim bisa berbagi satu IP (NAT kantor). Kombinasi keduanya:
from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
class CommentCreateView(APIView):
throttle_classes = [UserRateThrottle, AnonRateThrottle]
def post(self, request):
...Dengan kombinasi ini, batas terketat yang menang. DRF menyatukan beberapa throttle: request harus lolos semua throttle yang terpasang.
Tip
Default DRF memakai REMOTE_ADDR untuk anonim. Saat aplikasi di belakang Nginx/proxy (episode 23), semua anonim tampak berasal dari IP proxy! Konfigurasi NUM_PROXIES (via X-Forwarded-For) membuat Django menghitung IP asli. Lupa setting ini = satu anonim menghabiskan seluruh kuota anonim yang berbagi proxy yang sama.
Kebutuhan sering lebih spesifik dari bawaan: misal "maksimal 5 komentar per jam per user" memakai key unik. Buat custom throttle:
from rest_framework.throttling import SimpleRateThrottle
class CommentPerHourThrottle(SimpleRateThrottle):
scope = "comment_create"
def get_cache_key(self, request, view):
user = request.user
if not user.is_authenticated:
return None # tangani anonim lewat throttle lain
return self.cache_format % {
"scope": self.scope,
"ident": f"user:{user.pk}",
}REST_FRAMEWORK = {
"DEFAULT_THROTTLE_RATES": {
"comment_create": "5/hour",
}
}get_cache_key menentukan identitas yang dihitung — di sini user:<pk>. Jika mengembalikan None, throttle di-skip untuk request itu. Implementasi SimpleRateThrottle menyimpan counter di cache backend — memakai Redis dari episode 12 sehingga shared antar worker (episode 24).
Saat throttle tercapai, DRF mengembalikan status 429 Too Many Requests dengan header:
HTTP/1.1 429 Too Many Requests
Retry-After: 45
Content-Type: application/json
{"detail": "Request was throttled. Expected available in 45 seconds."}Client yang baik membaca Retry-After dan menunggu — pola backoff yang wajib dipahami untuk integrasi API. Test perilaku ini di kode:
from django.test import override_settings
from rest_framework import status
from rest_framework.test import APITestCase
@override_settings(
REST_FRAMEWORK={
"DEFAULT_THROTTLE_RATES": {"anon": "2/min"},
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
],
}
)
class AnonThrottleTests(APITestCase):
def test_anon_limited(self):
for _ in range(2):
self.client.get("/api/posts/")
self.assertEqual(self.client.get("/api/posts/").status_code,
status.HTTP_200_OK)
self.assertEqual(self.client.get("/api/posts/").status_code,
status.HTTP_429_TOO_MANY_REQUESTS)override_settings mengubah throttle rate hanya untuk durasi test — pola penting agar test tidak bergantung pada nilai produksi.
Brute-force login adalah target klasik. Throttle di endpoint login (/api/token/ JWT dari episode 11):
from rest_framework.throttling import AnonRateThrottle
from rest_framework_simplejwt.views import TokenObtainPairView
class ThrottledTokenObtainPairView(TokenObtainPairView):
throttle_classes = [AnonRateThrottle]
throttle_scope = "login"REST_FRAMEWORK = {
"DEFAULT_THROTTLE_RATES": {
"login": "5/min",
"anon": "100/day",
"user": "1000/hour",
}
}Batasan login: 5/min berarti satu IP hanya boleh mencoba 5 kali login per menit — brute-force menjadi sangat tidak efektif. Untuk pengamanan lebih dalam (lockout akun, 2FA), kita lanjut di episode 20.
Warning
Throttling bukan pengganti autentikasi yang benar — ia pengurang risiko. Kombinasikan dengan: password yang kuat, lockout setelah N kegagalan, dan monitoring alert saat 429 melonjak (observability episode 26). Dan selalu uji throttle di staging: rate yang terlalu agresif bisa memblokir user sah kalian sendiri.
Inti yang harus dibawa pulang:
AnonRateThrottle (IP) + UserRateThrottle (user) + ScopedRateThrottle (per-view/scope).5/min, 200/hour; throttle menumpuk — request harus lolos semuanya.SimpleRateThrottle + get_cache_key untuk identitas khusus.429 + Retry-After adalah kontrak yang dipahami client.Di episode 20 selanjutnya kita menuntaskan lapisan keamanan auth: Auth Lanjutan & Permissions — JWT refresh dan rotasi, OAuth2 dengan django-oauth-toolkit, MFA 2FA, serta object-level permission untuk API SPA yang aman. Sampai jumpa di episode 20!