API AUTHORIZATION · UNTUK TIM TEKNIS

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.

10 menit bacaCTO / Lead DeveloperRemediation blueprint
Batas sumber

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.
Actor
user A atau user B
Tenant
organisasi dari token terverifikasi
Object
invoice yang memiliki owner_id
Operation
read atau update
Decision
token valid + policy + object binding

Pseudo-code yang rentan

RENTANuser = verify_jwt(token) invoice = db.invoices.find(id) return invoice

Token diverifikasi, tetapi owner_id dan tenant tidak masuk ke query maupun policy.

LEBIH AMANuser = 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

A → AOwner A mengakses objek A: ALLOWED.
B → BOwner B mengakses objek B: ALLOWED.
ANON → ATanpa autentikasi: DENIED.
A → BOwner A mencoba objek B: harus DENIED.
STALE → ASession revoked atau stale: harus DENIED.

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.

PROOF GATEbaseline owner → success cross-owner mutation → one variable anonymous control → denied wrong/stale credential → denied authoritative readback → boundary confirmed cleanup → baseline restored

Remediation Blueprint

  • ikat query ke tenant_id dan owner_id dari 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.