Anatomi BOLA pada API:
Token Valid Bukan Hak atas Objek.
JWT yang valid hanya menjawab siapa aktornya. Server tetap harus membuktikan apakah aktor tersebut berhak melakukan operasi pada objek dan state tertentu.
Contoh invoice bersifat ilustratif. Mekanisme diadaptasi dari pengujian authorization terotorisasi: scope credential, parent-child lifecycle, dan owner authoritative readback. Tidak ada data pihak ketiga yang digunakan.
Endpoint GET /api/invoices/{id} dapat memverifikasi signature JWT dengan sempurna dan tetap rentan. Masalahnya terjadi ketika query hanya mencari invoice berdasarkan ID tanpa mengikatnya ke tenant atau owner dari token.
Invariant authorization
Aktor hanya boleh membaca atau mengubah objek miliknya, dalam tenant, role, state, transport, dan lifecycle yang diizinkan.
Pseudo-code yang rentan
user = verify_jwt(token)
invoice = db.invoices.find(id)
return invoiceToken diverifikasi, tetapi owner_id dan tenant tidak masuk ke query maupun policy.
user = verify_jwt(token)
invoice = db.invoices.find({
id: id,
tenant_id: user.tenant_id,
owner_id: user.id
})
if (!invoice) deny()Object binding menjadi bagian dari trust decision, bukan pemeriksaan client.
Matriks pembuktian minimum
Dalam engagement nyata, akun A dan B harus sama-sama dimiliki atau diotorisasi. Tanpa dua owned principals, model ini tetap desain pengujian—bukan temuan.
Mengapa mengganti ID saja belum cukup?
Perbedaan 200/403/404 dapat berasal dari anti-enumeration, cache, parser, object existence, atau policy yang benar. Boundary crossing baru terbukti jika respons atau durable state menunjukkan data/operasi objek pembanding benar-benar dapat diakses.
Pola segar: scope dan lifecycle
Pada kelas masalah terkait, token Restricted bekerja benar pada resource bisnis pembanding, tetapi keputusan credential tingkat akun tidak mewarisi scope yang sama. Pada pola lifecycle lain, credential sumber mati setelah revoke sementara descendant capability tetap mengubah state milik owner. Keduanya menunjukkan bahwa authorization harus konsisten lintas service dan waktu, bukan hanya pada endpoint utama.
baseline owner → success
cross-owner mutation → one variable
anonymous control → denied
wrong/stale credential → denied
authoritative readback → boundary confirmed
cleanup → baseline restoredRemediation Blueprint
- ikat query ke
tenant_iddanowner_iddari server-side identity; - gunakan policy object-level untuk setiap operasi, bukan middleware autentikasi saja;
- pisahkan permission read, update, delete, reveal, dan revoke;
- uji endpoint lama/baru, web/mobile/API, batch, export, dan background job;
- catat parent principal untuk credential turunan dan cascade-revoke saat authority berakhir;
- gunakan response generik tanpa mengorbankan pemeriksaan policy;
- tambahkan regression matrix A → A, B → B, A → B, anonymous, dan stale.
Kesimpulan
BOLA bukan masalah “ID dapat ditebak”. Masalahnya adalah trust decision yang tidak mengikat actor, tenant, object, operation, state, dan lifecycle. Token valid hanyalah satu elemen dari tuple kewenangan.