Memuat...
👋 Selamat Pagi!

Panduan Membangun API Rate Limiting untuk Melindungi Backend dari Abuse

Rate limiting adalah pertahanan pertama API Anda dari serangan dan abuse. Pelajari cara implementasi yang benar untuk melindungi backend production tanpa mengor...

Panduan Membangun API Rate Limiting untuk Melindungi Backend dari Abuse

Rate limiting adalah mekanisme keamanan yang membatasi jumlah request yang bisa dilakukan user dalam periode waktu tertentu.

Tanpa rate limiting, API Anda rentan terhadap serangan brute force, DDoS, dan abuse yang bisa membuat server down dalam hitungan menit.

Artikel ini akan membahas cara membangun sistem rate limiting yang efektif untuk melindungi backend production Anda.

Mengapa Rate Limiting Wajib Ada di Setiap API

Bayangkan seseorang mengirim 10.000 request login dalam satu menit ke API Anda.

Tanpa rate limiting, server akan memproses semua request tersebut, menghabiskan resource database, dan membuat aplikasi lambat bahkan crash untuk user lain.

Rate limiting melindungi infrastructure Anda dari berbagai jenis serangan dan abuse.

Jenis Serangan yang Dicegah Rate Limiting

Brute Force Attack adalah upaya menebak password dengan mencoba ribuan kombinasi secara otomatis.

Dengan rate limiting, attacker hanya bisa mencoba 5-10 kali per menit, membuat serangan ini tidak efektif.

DDoS (Distributed Denial of Service) mencoba membanjiri server dengan request massal dari berbagai IP.

Rate limiting berbasis IP dan global limit bisa memperlambat atau menghentikan serangan ini.

Web Scraping Abuse terjadi ketika bot mengambil data secara massal dari API Anda tanpa izin.

Rate limiting memastikan setiap client hanya bisa mengambil data dalam jumlah wajar.

API Cost Explosion bisa terjadi jika aplikasi third-party mengintegrasikan API Anda dengan cara yang tidak efisien.

Tanpa rate limiting, bug di aplikasi mereka bisa menghasilkan jutaan request dan membengkakkan biaya server Anda.

Strategi Rate Limiting yang Paling Efektif

Ada beberapa algoritma rate limiting yang umum digunakan, masing-masing dengan karakteristik berbeda.

Fixed Window Counter

Algoritma paling sederhana yang membatasi request dalam window waktu tetap.

Misalnya, maksimal 100 request per menit, dihitung dari menit 00:00 hingga 00:59.

// Pseudocode Fixed Window
window_start = current_minute
if counter[user_id][window_start] 

Kelemahan utama adalah burst traffic di pergantian window.

User bisa mengirim 100 request di detik ke-59, lalu 100 request lagi di detik ke-00, total 200 request dalam 2 detik.

Sliding Window Log

Algoritma yang menyimpan timestamp setiap request dalam log.

Setiap request baru, sistem menghitung berapa request yang terjadi dalam 60 detik terakhir dari waktu sekarang.

// Pseudocode Sliding Window Log
remove_old_requests(user_id, current_time - 60_seconds)
if count_requests(user_id) 

Algoritma ini sangat akurat tapi membutuhkan memory lebih besar karena menyimpan setiap timestamp.

Token Bucket

Algoritma yang paling populer dan digunakan oleh AWS, Google Cloud, dan sistem enterprise.

Setiap user memiliki "bucket" yang berisi token, setiap request mengambil 1 token.

Token diisi ulang secara konstan dengan rate tertentu, misalnya 10 token per detik.

// Pseudocode Token Bucket
bucket_capacity = 100
refill_rate = 10 per second

if bucket[user_id].tokens >= 1:
    bucket[user_id].tokens--
    allow_request()
else:
    reject_request()

// Background process
every_second:
    bucket[user_id].tokens = min(
        bucket[user_id].tokens + refill_rate,
        bucket_capacity
    )

Token bucket memungkinkan burst traffic dalam batas wajar sambil menjaga average rate tetap terkontrol.

Leaky Bucket

Mirip token bucket, tapi fokus pada output rate yang konstan.

Request masuk ditampung dalam queue, lalu diproses dengan rate tetap seperti air yang bocor dari ember.

Algoritma ini bagus untuk menghaluskan traffic spike, cocok untuk backend yang tidak bisa handle burst traffic.

Implementasi Rate Limiting di Laravel

Laravel sudah menyediakan rate limiting built-in yang powerful dan mudah digunakan.

Rate Limiting Dasar dengan Middleware

Laravel menggunakan token bucket algorithm secara default.

// routes/api.php
Route::middleware('throttle:60,1')->group(function () {
    Route::get('/user', [UserController::class, 'index']);
    Route::post('/orders', [OrderController::class, 'store']);
});

Parameter throttle:60,1 berarti maksimal 60 request per 1 menit per user.

Laravel secara otomatis mengidentifikasi user berdasarkan authenticated user ID atau IP address untuk guest.

Custom Rate Limit untuk Endpoint Berbeda

Tidak semua endpoint membutuhkan limit yang sama.

Login endpoint harus lebih ketat dari endpoint read-only seperti get profile.

// app/Providers/RouteServiceProvider.php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

protected function configureRateLimiting()
{
    // Login endpoint: 5 attempts per minute
    RateLimiter::for('login', function (Request $request) {
        return Limit::perMinute(5)
            ->by($request->ip())
            ->response(function () {
                return response()->json([
                    'message' => 'Terlalu banyak percobaan login. Coba lagi dalam 1 menit.'
                ], 429);
            });
    });
    
    // API general: 100 requests per minute
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(100)
            ->by($request->user()?->id ?: $request->ip());
    });
    
    // Heavy operation: 10 per minute
    RateLimiter::for('heavy', function (Request $request) {
        return Limit::perMinute(10)
            ->by($request->user()->id);
    });
}

Kemudian aplikasikan di routes:

Route::post('/login', [AuthController::class, 'login'])
    ->middleware('throttle:login');

Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
    Route::get('/users', [UserController::class, 'index']);
});

Route::post('/export-large-data', [ExportController::class, 'export'])
    ->middleware(['auth:sanctum', 'throttle:heavy']);

Rate Limiting Berbasis User Tier

Aplikasi SaaS biasanya memberikan limit berbeda untuk free user vs premium user.

RateLimiter::for('api-tiered', function (Request $request) {
    $user = $request->user();
    
    if (!$user) {
        return Limit::perMinute(10)->by($request->ip());
    }
    
    // Premium users get higher limit
    if ($user->subscription_tier === 'premium') {
        return Limit::perMinute(1000)->by($user->id);
    }
    
    // Free users get standard limit
    return Limit::perMinute(100)->by($user->id);
});

Kesulitan dengan tugas programming atau butuh bantuan coding? KerjaKode siap membantu menyelesaikan tugas IT dan teknik informatika Anda. Dapatkan bantuan profesional di jasa tugas IT KerjaKode.

Implementasi Rate Limiting di Node.js/Express

Untuk Node.js, library express-rate-limit adalah pilihan paling populer.

Setup Basic Rate Limiting

const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const redis = require('redis');

const redisClient = redis.createClient({
    host: process.env.REDIS_HOST,
    port: process.env.REDIS_PORT
});

// General API rate limiter
const apiLimiter = rateLimit({
    store: new RedisStore({
        client: redisClient,
        prefix: 'rl:api:'
    }),
    windowMs: 60 * 1000, // 1 minute
    max: 100, // 100 requests per window
    message: {
        error: 'Terlalu banyak request. Silakan coba lagi nanti.'
    },
    standardHeaders: true,
    legacyHeaders: false,
});

// Login rate limiter (stricter)
const loginLimiter = rateLimit({
    store: new RedisStore({
        client: redisClient,
        prefix: 'rl:login:'
    }),
    windowMs: 60 * 1000,
    max: 5,
    skipSuccessfulRequests: true, // only count failed attempts
    message: {
        error: 'Terlalu banyak percobaan login. Coba lagi dalam 1 menit.'
    }
});

// Apply to routes
app.use('/api/', apiLimiter);
app.post('/api/auth/login', loginLimiter, authController.login);

Rate Limiting dengan Custom Key

Anda bisa membuat logic custom untuk menentukan identifier yang digunakan rate limiting.

const customLimiter = rateLimit({
    store: new RedisStore({ client: redisClient }),
    windowMs: 60 * 1000,
    max: async (req) => {
        // Different limit based on user tier
        if (req.user?.tier === 'premium') return 1000;
        if (req.user?.tier === 'standard') return 100;
        return 10; // guest/free
    },
    keyGenerator: (req) => {
        // Use user ID if authenticated, otherwise IP
        return req.user?.id || req.ip;
    },
    handler: (req, res) => {
        const resetTime = new Date(req.rateLimit.resetTime);
        res.status(429).json({
            error: 'Rate limit exceeded',
            retryAfter: Math.ceil((resetTime - Date.now()) / 1000),
            limit: req.rateLimit.limit,
            remaining: req.rateLimit.remaining
        });
    }
});

Menggunakan Redis untuk Rate Limiting di Scale

Ketika aplikasi Anda berjalan di multiple server, rate limiting harus centralized.

Redis adalah pilihan terbaik karena cepat, support atomic operations, dan memiliki TTL otomatis.

Implementasi Token Bucket dengan Redis

// PHP/Laravel example using Redis
use Illuminate\Support\Facades\Redis;

class TokenBucketRateLimiter
{
    protected $capacity = 100;
    protected $refillRate = 10; // per second
    
    public function attempt(string $key): bool
    {
        $bucket = "rate_limit:token_bucket:{$key}";
        $now = time();
        
        // Get current bucket state
        $data = Redis::hgetall($bucket);
        $tokens = (float) ($data['tokens'] ?? $this->capacity);
        $lastRefill = (int) ($data['last_refill'] ?? $now);
        
        // Calculate tokens to add based on time passed
        $timePassed = $now - $lastRefill;
        $tokensToAdd = $timePassed * $this->refillRate;
        $tokens = min($this->capacity, $tokens + $tokensToAdd);
        
        // Try to consume 1 token
        if ($tokens >= 1) {
            $tokens--;
            Redis::hmset($bucket, [
                'tokens' => $tokens,
                'last_refill' => $now
            ]);
            Redis::expire($bucket, 3600); // cleanup after 1 hour
            return true;
        }
        
        return false;
    }
    
    public function remaining(string $key): float
    {
        $bucket = "rate_limit:token_bucket:{$key}";
        $data = Redis::hgetall($bucket);
        
        if (empty($data)) {
            return $this->capacity;
        }
        
        $tokens = (float) $data['tokens'];
        $lastRefill = (int) $data['last_refill'];
        $timePassed = time() - $lastRefill;
        $tokensToAdd = $timePassed * $this->refillRate;
        
        return min($this->capacity, $tokens + $tokensToAdd);
    }
}

Sliding Window dengan Redis Sorted Set

Redis sorted set sangat cocok untuk implementasi sliding window log.

// Node.js example
class SlidingWindowRateLimiter {
    constructor(redisClient, windowMs, maxRequests) {
        this.redis = redisClient;
        this.windowMs = windowMs;
        this.maxRequests = maxRequests;
    }
    
    async attempt(key) {
        const now = Date.now();
        const windowStart = now - this.windowMs;
        const rateLimitKey = `rate_limit:sliding:${key}`;
        
        // Remove old entries outside the window
        await this.redis.zremrangebyscore(rateLimitKey, 0, windowStart);
        
        // Count requests in current window
        const requestCount = await this.redis.zcard(rateLimitKey);
        
        if (requestCount  0 
            ? Math.ceil((parseInt(oldestRequest[1]) + this.windowMs - now) / 1000)
            : Math.ceil(this.windowMs / 1000);
        
        return { allowed: false, remaining: 0, retryAfter };
    }
}

Response Headers untuk Rate Limiting

API yang baik harus memberikan informasi rate limit di response headers.

Client bisa menggunakannya untuk adaptive throttling dan memberikan feedback ke user.

Standard Headers

X-RateLimit-Limit: 100          // Total limit in current window
X-RateLimit-Remaining: 87       // Remaining requests
X-RateLimit-Reset: 1719993600   // Unix timestamp when limit resets
Retry-After: 45                 // Seconds until retry (on 429 response)

Implementasi di Laravel middleware:

public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
{
    $key = $this->resolveRequestSignature($request);
    $maxAttempts = $this->resolveMaxAttempts($request, $maxAttempts);
    
    if ($this->limiter->tooManyAttempts($key, $maxAttempts)) {
        $retryAfter = $this->limiter->availableIn($key);
        
        return response()->json([
            'message' => 'Too Many Attempts.'
        ], 429)->withHeaders([
            'X-RateLimit-Limit' => $maxAttempts,
            'X-RateLimit-Remaining' => 0,
            'X-RateLimit-Reset' => time() + $retryAfter,
            'Retry-After' => $retryAfter,
        ]);
    }
    
    $this->limiter->hit($key, $decayMinutes * 60);
    
    $response = $next($request);
    
    return $response->withHeaders([
        'X-RateLimit-Limit' => $maxAttempts,
        'X-RateLimit-Remaining' => $this->limiter->remaining($key, $maxAttempts),
    ]);
}

Rate Limiting untuk Different Attack Vectors

Setiap jenis endpoint membutuhkan strategi rate limiting yang berbeda.

Authentication Endpoints

Login dan register adalah target utama brute force attack.

// Multiple layers of protection
// 1. Per-IP limit (prevent distributed attack)
RateLimiter::for('login-ip', fn($req) => 
    Limit::perMinute(10)->by($req->ip())
);

// 2. Per-email limit (prevent targeted attack)
RateLimiter::for('login-email', fn($req) => 
    Limit::perMinute(3)->by($req->input('email'))
);

// 3. Global limit (prevent massive distributed attack)
RateLimiter::for('login-global', fn($req) => 
    Limit::perMinute(1000)
);

// Apply all limits
Route::post('/login', [AuthController::class, 'login'])
    ->middleware([
        'throttle:login-ip',
        'throttle:login-email',
        'throttle:login-global'
    ]);

Search Endpoints

Search biasanya operasi yang expensive karena melibatkan database query kompleks.

RateLimiter::for('search', function (Request $request) {
    // Authenticated users get more generous limit
    if ($request->user()) {
        return Limit::perMinute(30)->by($request->user()->id);
    }
    
    // Anonymous users more restricted
    return Limit::perMinute(5)->by($request->ip());
});

File Upload Endpoints

Upload bisa menghabiskan bandwidth dan storage dengan cepat.

RateLimiter::for('upload', function (Request $request) {
    return [
        Limit::perMinute(5)->by($request->user()->id),
        Limit::perHour(20)->by($request->user()->id)
    ];
});

Payment Endpoints

Payment endpoint perlu rate limit ketat untuk mencegah fraud dan duplicate charges.

RateLimiter::for('payment', function (Request $request) {
    return [
        // Max 3 payment attempts per minute
        Limit::perMinute(3)->by($request->user()->id),
        // Max 10 payment attempts per hour
        Limit::perHour(10)->by($request->user()->id)
    ];
});

Monitoring dan Alerting Rate Limit

Rate limiting bukan set-and-forget, Anda perlu monitoring untuk detect anomali.

Metrics yang Harus Dimonitor

Rate Limit Hit Rate menunjukkan berapa persen request yang kena rate limit.

Jika angkanya tinggi (>5%), mungkin limit Anda terlalu ketat atau ada serangan sedang berlangsung.

Top Rate Limited IPs/Users membantu identifikasi source abuse.

IP yang konsisten kena rate limit kemungkinan bot atau attacker.

Rate Limit by Endpoint menunjukkan endpoint mana yang paling sering kena limit.

Ini membantu optimize limit per endpoint.

Logging Rate Limit Events

// Laravel example
RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(100)
        ->by($request->user()?->id ?: $request->ip())
        ->response(function (Request $request, array $headers) {
            // Log rate limit hit
            Log::warning('Rate limit exceeded', [
                'ip' => $request->ip(),
                'user_id' => $request->user()?->id,
                'endpoint' => $request->path(),
                'user_agent' => $request->userAgent(),
                'headers' => $headers
            ]);
            
            // Increment metric
            Metrics::increment('rate_limit.hits', [
                'endpoint' => $request->path()
            ]);
            
            return response()->json([
                'message' => 'Too many requests'
            ], 429);
        });
});

Bypass Rate Limiting untuk Internal Services

Microservices internal atau monitoring tools tidak boleh kena rate limit.

RateLimiter::for('api', function (Request $request) {
    // Whitelist internal IPs
    $internalIPs = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'];
    
    foreach ($internalIPs as $range) {
        if ($this->ipInRange($request->ip(), $range)) {
            return Limit::none();
        }
    }
    
    // Check for internal API key
    if ($request->header('X-Internal-API-Key') === config('app.internal_api_key')) {
        return Limit::none();
    }
    
    // Normal rate limiting for public requests
    return Limit::perMinute(100)->by($request->user()?->id ?: $request->ip());
});

private function ipInRange($ip, $range)
{
    list($subnet, $bits) = explode('/', $range);
    $ip = ip2long($ip);
    $subnet = ip2long($subnet);
    $mask = -1 

Testing Rate Limiting

Rate limiting harus ditest secara menyeluruh sebelum production.

Unit Test

// Laravel test example
public function test_rate_limiting_blocks_excessive_requests()
{
    $user = User::factory()->create();
    
    // Make 100 requests (within limit)
    for ($i = 0; $i actingAs($user)->getJson('/api/users');
        $response->assertStatus(200);
    }
    
    // 101st request should be rate limited
    $response = $this->actingAs($user)->getJson('/api/users');
    $response->assertStatus(429);
    $response->assertHeader('X-RateLimit-Limit', 100);
    $response->assertHeader('X-RateLimit-Remaining', 0);
}

public function test_rate_limit_resets_after_window()
{
    $user = User::factory()->create();
    
    // Exhaust limit
    for ($i = 0; $i actingAs($user)->getJson('/api/users');
    }
    
    // Should be blocked
    $response = $this->actingAs($user)->getJson('/api/users');
    $response->assertStatus(429);
    
    // Travel forward 61 seconds
    $this->travel(61)->seconds();
    
    // Should work again
    $response = $this->actingAs($user)->getJson('/api/users');
    $response->assertStatus(200);
}

Load Testing

Gunakan tools seperti Apache Bench atau k6 untuk test rate limiting under load.

// k6 load test script
import http from 'k6/http';
import { check, sleep } from 'k6';

export let options = {
    stages: [
        { duration: '30s', target: 50 }, // Ramp up to 50 users
        { duration: '1m', target: 50 },  // Stay at 50 users
        { duration: '30s', target: 0 },  // Ramp down
    ],
    thresholds: {
        'http_req_duration': ['p(95) r.status === 200,
        'status is 429': (r) => r.status === 429,
    });
    
    // Track rate limiting
    if (res.status === 429) {
        rateLimited.add(1);
    }
    
    sleep(1);
}

Best Practices Rate Limiting

Gunakan algoritma yang tepat untuk use case Anda. Token bucket untuk most cases, sliding window untuk accurate control, leaky bucket untuk smooth traffic.

Jangan terlalu ketat di awal. Mulai dengan limit generous, monitor usage pattern, lalu adjust berdasarkan data.

Berikan feedback yang jelas ke client. Selalu include rate limit headers dan error message yang informatif.

Implement graceful degradation. Ketimbang hard reject, pertimbangkan slow down response atau queue request untuk user premium.

Different limits for different operations. Read operations bisa lebih generous dibanding write operations.

Monitor dan adjust secara berkala. Traffic pattern berubah seiring waktu, rate limit harus di-review dan di-adjust.

Test under production-like load. Rate limiting behavior bisa berbeda di high load, jangan hanya test dengan traffic rendah.

Document rate limits di API documentation. Developer yang mengintegrasikan API Anda harus tahu limit apa yang berlaku.

Kesimpulan

Rate limiting adalah komponen security yang kritikal untuk setiap API production.

Tanpa rate limiting, backend Anda vulnerable terhadap berbagai attack dan abuse yang bisa menyebabkan downtime dan biaya server meledak.

Pilih algoritma yang sesuai dengan karakteristik aplikasi Anda, implementasikan dengan benar menggunakan Redis untuk distributed systems, dan monitor secara continuous.

Rate limiting yang efektif melindungi infrastructure Anda sambil tetap memberikan experience yang baik untuk legitimate users.

Mulai dengan limit yang reasonable, monitor usage pattern, dan adjust based on data untuk menemukan sweet spot antara security dan usability.

Ajie Kusumadhany
Written by

Ajie Kusumadhany

Founder & Lead Developer KerjaKode. Berpengalaman dalam pengembangan web modern dengan Laravel, React.js, Vue.js, dan teknologi terkini. Passionate tentang coding, teknologi, dan berbagi pengetahuan melalui artikel.

Promo Spesial Hari Ini!

10% DISKON

Promo berakhir dalam:

00 Jam
:
00 Menit
:
00 Detik
Klaim Promo Sekarang!

*Promo berlaku untuk order hari ini

0
User Online
Halo! 👋
Kerjakode Support Online
×

👋 Hai! Pilih layanan yang kamu butuhkan:

Chat WhatsApp Sekarang