Mengamankan API modern dari autentikasi token dan scope-based authorization, rate limiting berlapis, hingga OWASP API Top 10 dengan fokus BOLA dan mass assignment; ditutup praktik testing API sistematis menggunakan curl, Burp, dan collection otomatis

Setelah di episode 5 kita mengeraskan autentikasi dan sesi, sekarang kita terapkan pada permukaan yang paling luas di produk modern: API. Mengapa API layak episode tersendiri? Karena pola serangannya berbeda dari web klasik — tidak ada manusia mengeksploitasi via browser, melainkan script otomatis yang memanggil endpoint langsung. UI kalian bisa seaman apa pun; jika API-nya bocor, produknya bocor. OWASP bahkan menerbitkan daftar khusus: OWASP API Security Top 10, karena profil risikonya memang unik.
Kita bahas: model autentikasi & otorisasi API, rate limiting, kategori-kategori penting API Top 10, lalu ditutup dengan metodologi testing API.
| Skema | Cocok Untuk | Catatan |
|---|---|---|
| API key (header) | Server-to-server sederhana | Jangan pernah expose ke frontend |
| Bearer JWT pendek + refresh | SPA/mobile | Umur access 5–15 menit |
| OAuth2 client credentials | Service-to-service enterprise | Rotasi kunci wajib |
| mTLS | Internal high-trust | Identitas kuat dua arah |
Aturan mainnya konsisten dengan episode 5: access token umur pendek, refresh token revocable, scope minimal.
Bedakan authentication (token valid?) dari authorization (token ini boleh melakukan aksi ini?). Middleware harus memverifikasi scope per endpoint:
function requireScope(scope: string) {
return (req, res, next) => {
if (!req.auth.scopes.includes(scope)) {
return res.status(403).json({ error: "insufficient_scope" });
}
next();
};
}
app.delete("/api/users/:id", requireAuth, requireScope("users:delete"), handler);Tanpa granularity seperti ini, satu token "superuser" akan tersebar ke semua integrasi partner — dan saat itu bocor, semuanya terbuka.
API publik tanpa rate limit adalah undangan scraping, brute force, dan DoS ekonomi. Strategi berlapis:
Algoritma umum: fixed window (sederhana, bursty di batas window), sliding window (lebih halus), token bucket (memungkinkan burst terkendali). Untuk Redis, pola INCR+EXPIRE atau library rate-limiter-flexible cukup untuk mayoritas kasus:
const attempts = await redis.incr(`login:${email}`);
if (attempts === 1) await redis.expire(`login:${email}`, 900);
if (attempts > 5) return res.status(429).json({ error: "too_many_attempts" });Respons 429 wajib disertai header Retry-After. Dan ingat pelajaran episode 5: limit per IP saja mudah dilewati botnet — kombinasikan identitas akun.
Daftar lengkapnya berubah tiap edisi, tapi tiga kategori mendominasi temuan nyata:
Padanan A01 di dunia API dan temuan nomor satu di hampir setiap pentest API:
GET /api/v2/orders/8891 HTTP/1.1 ← ID object milik user lain
Authorization: Bearer eyJ... ← token user AServer mengembalikan data order 8891 milik user B hanya karena token valid. Mitigasinya persis pilar 3 episode 4: object-level authorization check per request, diuji otomatis dengan dua akun berbeda. Automasi ujinya sederhana: jalankan suite request dengan token A tetapi ID resource milik B — semua harus 403/404.
Framework binding yang cerdas menjadi celah ketika payload klien dipetakan langsung ke model:
{ "name": "Legit User", "role": "admin", "isVerified": true }Jika controller melakukan User.update(req.body) tanpa whitelist field, penyerang meng-elevate dirinya sendiri. Mitigasi: DTO eksplisit — definisikan field mana saja yang diterima per endpoint, abaikan sisanya. Di NestJS gunakan ValidationPipe + whitelist: true; di Django, forms/serializers fields eksplisit.
Varian API-specific: token tanpa expiry, endpoint debug (/api/debug, /actuator) terekspos produksi, dan respons yang membocorkan properti sensitif (password_hash ikut ter-serialize). Untuk kasus terakhir, serializer per-endpoint dengan allowlist properti adalah jawabannya — jangan pernah serialize model ORM mentah.
Warning
Dokumentasi OpenAPI/Swagger yang terekspos publik adalah peta harta karun bagi penyerang: semua endpoint, parameter, dan kadang contoh kredensial. Jika harus publik, pastikan tidak ada environment production di dalamnya; idealnya proteksi dengan auth.
Metodologi lima langkah yang saya pakai saat review API baru:
1. Petakan surface dari dokumentasi/kontrak
jq -r '.paths | keys[]' openapi.json | sortEndpoint yang ada di kode tapi tidak ada di kontrak = shadow API — temuan tersendiri.
2. Uji autentikasi: panggil semua endpoint tanpa token, token expired, token scope salah. Semua harus 401/403, bukan 500.
3. Uji otorisasi objek: replay seluruh collection dengan token akun A namun ID resource akun B.
4. Uji input boundary: tipe salah, nilai ekstrem, field tambahan (mass assignment), content-type aneh.
curl -s -H "Authorization: Bearer $TOKEN_A" https://api.example.com/v1/orders/$ORDER_ID_B -o /dev/null -w "%{http_code}\n"
# Harapkan: 403 atau 404 — bukan 2005. Uji abuse logic: rate limit benar-benar bekerja? Refund bisa dipanggil dua kali? Nilai negatif diterima?
Untuk regresi, simpan semua skenario ini sebagai collection Postman/Bruno atau test HTTP file di repo — dan jalankan sebagai integration security test di CI (episode 11).
Tip
Bruno/Postman collection dengan skrip test assertion 401/403 adalah versi murah dari DAST API. Satu sore menyusunnya memberi perlindungan regresi otorisasi permanen untuk tim kalian.
/v1 rentan hidup karena "masih ada klien" adalah utang berbunga; tetapkan sunset date.Inti yang harus dibawa pulang:
Di episode 7 kita mulai fase tooling: SAST & Static Analysis — bagaimana mesin membaca kode untuk menemukan kerentanan, cara menulis rule Semgrep sendiri, dan integrasinya ke pull request tanpa membanjiri developer false positive. Sampai jumpa di episode 7!