Saat layanan autentikasi atau dependensi eksternal bermasalah, regresi kecil pada API auth bisa berubah menjadi insiden besar: login gagal total, token valid ditolak, retry memperparah beban, atau fallback justru membuka celah keamanan. Karena itu, strategi uji API auth untuk cegah regresi saat dependensi bermasalah tidak cukup mengandalkan happy path dan integration test dasar.
Pendekatan yang lebih aman adalah memecah pengujian ke beberapa lapisan: logika lokal, kontrak dengan provider eksternal, simulasi gangguan jaringan dan respons error, serta verifikasi perilaku sistem saat rilis dan rollback. Tujuannya bukan hanya memastikan autentikasi bekerja saat normal, tetapi juga memastikan sistem gagal dengan cara yang terkontrol, dapat diamati, dan tidak merusak keamanan.
Risiko utama pada API auth ketika dependensi eksternal gagal
Auth flow hampir selalu bergantung pada komponen di luar proses utama aplikasi: identity provider, server introspeksi token, JWKS endpoint, cache, DNS, load balancer, atau service internal lain. Gangguan di salah satu titik ini dapat memicu regresi yang sulit terdeteksi jika test terlalu sempit.
- Timeout: aplikasi menggantung terlalu lama, thread atau worker habis, antrean request menumpuk.
- 401 Unauthorized: token memang tidak valid, tetapi bisa juga terjadi karena bug parsing header, clock skew, atau kegagalan refresh token.
- 403 Forbidden: autentikasi berhasil, tetapi otorisasi salah membaca scope, role, atau policy.
- 5xx dari provider: sistem harus membedakan error sementara dengan kondisi token benar-benar tidak sah.
- Token expired: alur refresh gagal, refresh loop tanpa batas, atau token lama masih dipakai setelah rotasi.
- Ketergantungan pada cache: cache stale membuat token yang sudah dicabut tetap diterima, atau cache miss massal membebani provider.
Masalah-masalah ini sering tidak muncul di test lokal biasa karena lingkungan terlalu bersih: latensi rendah, clock sinkron, tidak ada kegagalan DNS, dan mock yang terlalu ideal.
Test pyramid untuk auth flow: apa yang diuji di tiap lapisan
Kesalahan umum adalah menaruh hampir semua validasi auth di integration atau end-to-end test. Hasilnya lambat, mahal, dan sulit menelusuri sumber regresi. Untuk auth, test pyramid tetap relevan, tetapi isi tiap lapisan harus dirancang sesuai jenis risiko.
1. Unit test: logika deterministik dan keputusan keamanan
Lapisan ini menguji keputusan lokal yang tidak perlu jaringan. Fokus pada bagian yang jika rusak akan menyebabkan penerimaan atau penolakan request secara salah.
- Parsing header
Authorizationdan format bearer token. - Validasi klaim token: issuer, audience, expiry, not-before, scope, subject.
- Pemetaan status error internal ke respons API yang benar.
- Keputusan retry: request mana yang boleh dicoba ulang dan mana yang tidak.
- Perilaku fallback: kapan cache boleh dipakai dan kapan harus fail closed.
Unit test harus deterministic. Hindari akses waktu sistem langsung; gunakan abstraction time source agar token expired mudah diuji tanpa sleep.
// Pseudocode universal: evaluasi token tanpa tergantung jam sistem langsung
function validateTokenClaims(claims, now, expectedIssuer, expectedAudience) {
if (claims.iss != expectedIssuer) return "invalid_issuer"
if (!claims.aud.contains(expectedAudience)) return "invalid_audience"
if (claims.nbf != null && now < claims.nbf) return "not_yet_valid"
if (claims.exp == null || now >= claims.exp) return "expired"
return "ok"
}Catatan: untuk pengujian expiry, gunakan waktu tetap atau clock injection. Test yang menunggu token benar-benar kedaluwarsa cenderung lambat dan flaky.
2. Integration test: integrasi internal dan penyimpanan lokal
Lapisan ini menguji komponen nyata di dalam boundary sistem Anda, misalnya middleware auth, session store, cache token, local key store, atau policy engine. Tujuannya memastikan wiring internal bekerja tanpa harus bergantung pada layanan pihak ketiga yang benar-benar hidup.
Contoh yang layak diuji di integration level:
- Middleware menolak request tanpa token dan mengembalikan struktur error yang konsisten.
- Token valid menghasilkan principal/user context yang benar.
- Cache introspeksi menyimpan hasil dengan TTL aman dan tidak melewati masa valid token.
- Token revoked tidak lolos ketika cache dibersihkan atau saat refresh data dilakukan.
3. Contract test: kontrak dengan provider auth eksternal
Inilah lapisan yang sering diabaikan. Banyak tim hanya mem-mock provider auth secara longgar, lalu kaget saat provider mengubah struktur respons, header penting, atau pola error. Contract test ke provider eksternal berguna untuk memastikan asumsi integrasi tetap benar.
Yang perlu dikunci dalam contract test:
- Format respons sukses dan error yang benar-benar dipakai aplikasi.
- Field yang wajib ada, misalnya subject, expiry, scopes, issuer.
- Header atau metadata yang memengaruhi cache atau retry.
- Status code yang harus dibedakan: 401 vs 403 vs 429 vs 5xx.
- Perilaku endpoint metadata seperti JWKS atau introspeksi token.
Contract test tidak harus banyak. Lebih baik beberapa skenario yang kritis dan stabil daripada suite besar yang rapuh. Bila provider menyediakan sandbox, gunakan untuk validasi kontrak terbatas. Bila tidak, gunakan mock server yang dibangun dari contoh respons nyata dan diperbarui secara disiplin.
4. End-to-end test: sedikit, fokus, dan tidak mencoba menutup semua risiko
E2E auth tetap perlu, tetapi porsinya kecil. Gunakan untuk memverifikasi jalur bisnis terpenting: request dengan token valid diterima, request tanpa token ditolak, dan role tertentu tidak bisa mengakses resource sensitif. Jangan menumpuk semua skenario timeout, rotasi kunci, dan error provider di level ini karena akan membuat suite lambat dan sulit dipelihara.
Skenario gangguan yang wajib disimulasikan
Pengujian auth yang matang harus menguji kegagalan sebagai perilaku yang diharapkan, bukan sekadar kondisi acak. Berikut skenario yang layak masuk ke matriks uji.
Timeout dan kegagalan jaringan
- Provider auth tidak merespons dalam batas waktu koneksi.
- Respons lambat melebihi request timeout aplikasi.
- Kegagalan DNS atau koneksi terputus sebelum respons diterima.
- Pool koneksi habis akibat lonjakan retry.
Yang diverifikasi:
- Request berakhir dengan status yang konsisten sesuai kebijakan sistem.
- Timeout tidak lebih besar dari SLA internal yang ditetapkan.
- Retry tidak dilakukan tanpa batas.
- Log dan metric mencatat dependency timeout secara terpisah dari invalid token.
401, 403, dan 5xx harus dibedakan dengan jelas
Kesalahan umum adalah memperlakukan semua kegagalan autentikasi sebagai 401. Padahal maknanya berbeda:
- 401: identitas tidak dapat diverifikasi atau token tidak valid.
- 403: identitas valid, tetapi tidak berhak melakukan aksi.
- 5xx atau timeout dependency: masalah ada di sisi provider atau infrastruktur, bukan otomatis kesalahan klien.
Perbedaan ini penting untuk keamanan, debugging, dan pengalaman integrator API. Jika 5xx provider diterjemahkan menjadi 401, klien akan keliru mengira kredensial salah dan mungkin memicu refresh atau login ulang yang tidak perlu.
Token expired, refresh, dan clock skew
Token expired adalah sumber regresi klasik, terutama saat ada refresh token atau validasi dengan jam dari beberapa node berbeda.
Uji minimal yang perlu ada:
- Access token expired ditolak dengan benar.
- Refresh token valid menghasilkan access token baru.
- Refresh token expired atau revoked ditolak.
- Clock skew kecil masih ditoleransi sesuai kebijakan sistem.
- Token baru menggantikan token lama secara atomik di klien atau gateway internal.
// Pseudocode: retry hanya untuk error sementara, bukan untuk token invalid
function shouldRetryAuthRequest(error) {
if (error.type == "timeout") return true
if (error.httpStatus >= 500) return true
if (error.httpStatus == 429) return true
if (error.httpStatus == 401) return false
if (error.httpStatus == 403) return false
return false
}Retry untuk 401 hampir selalu salah jika token memang invalid. Yang lebih tepat adalah memicu alur refresh atau meminta kredensial baru, bukan mengulang request yang sama berkali-kali.
Retry yang aman, fallback, dan fail-open vs fail-closed
Saat dependensi auth bermasalah, keputusan desain paling sensitif adalah: apakah sistem menolak semua request, menggunakan cache, atau membiarkan sebagian traffic lewat? Tidak ada jawaban tunggal; pilihan tergantung risiko keamanan dan dampak bisnis.
Retry yang aman
Retry aman untuk gangguan sementara, tetapi berbahaya jika tidak dibatasi.
- Retry hanya untuk timeout, 429, atau 5xx tertentu.
- Gunakan batas jumlah retry dan jeda bertahap.
- Tambahkan jitter agar banyak instance tidak mengulang serentak.
- Hormati idempotensi. Jangan mengulang operasi mutasi yang mengandung side effect jika autentikasi bercampur dengan eksekusi bisnis dalam satu request.
Pemisahan antara authenticate first dan eksekusi handler bisnis membantu menghindari pengulangan side effect saat retry terjadi di layer gateway atau middleware.
Fallback yang aman
Fallback umum pada auth adalah menggunakan cache hasil introspeksi atau kunci publik lokal saat provider tidak tersedia. Ini bisa masuk akal, tetapi hanya jika batasnya jelas.
- Gunakan cache hanya untuk token yang sebelumnya sudah tervalidasi.
- TTL cache tidak boleh melebihi masa berlaku token.
- Untuk token yang membutuhkan status revocation real-time, fallback cache memiliki risiko keamanan lebih tinggi.
- Jika tidak ada dasar kepercayaan lokal yang cukup, pilih fail closed daripada menerima request tanpa verifikasi.
Fail-open pada auth hampir selalu keputusan berisiko tinggi. Jika dipakai untuk endpoint tertentu, keputusan itu harus eksplisit, terdokumentasi, dan diuji khusus. Misalnya endpoint kesehatan internal mungkin boleh tetap berjalan, tetapi endpoint akses data pengguna sebaiknya tidak.
Mengurangi flaky test dengan mock terkontrol dan environment reproducible
Flaky test pada auth sering berasal dari ketergantungan waktu, jaringan, atau data sandbox yang berubah. Solusinya bukan mengurangi skenario sulit, melainkan mengendalikan sumber nondeterminisme.
Prinsip mock yang benar untuk auth
- Mock harus merepresentasikan kontrak penting, bukan sekadar selalu mengembalikan 200.
- Mock perlu mendukung skenario timeout, 401, 403, 429, 500, respons lambat, dan payload tidak lengkap.
- Mock harus bisa diatur per test case, bukan berbagi state antar test.
- Jika ada rotasi kunci atau perubahan metadata, buat fixture yang menggambarkan sebelum dan sesudah rotasi.
Mock yang terlalu pintar juga berbahaya. Jika mock berisi logika yang berbeda dari implementasi provider nyata, Anda hanya memindahkan bug ke tempat lain. Karena itu, gabungkan mock terkontrol dengan contract test yang memverifikasi asumsi penting terhadap provider sesungguhnya.
Lingkungan uji yang reproducible
- Kunci waktu dengan fake clock atau fixed timestamp.
- Gunakan data token fixture yang eksplisit: valid, expired, wrong audience, wrong issuer, missing scope.
- Isolasi cache, storage, dan secret antar test run.
- Pastikan konfigurasi timeout test lebih pendek dari default produksi agar kegagalan cepat terlihat.
- Jangan berbagi credential sandbox antar pipeline tanpa isolasi, karena rate limit dan data bersama sering memicu flaky test.
Tip debugging: jika test auth gagal acak, cek tiga hal lebih dulu: sumber waktu, state cache/shared storage, dan apakah mock server mewarisi state dari test sebelumnya.
Matriks skenario uji yang praktis
Daripada menulis daftar test panjang tanpa prioritas, gunakan matriks sederhana untuk memetakan kondisi, ekspektasi, dan observability yang harus muncul.
| Skenario | Input/Kondisi | Ekspektasi API | Yang diverifikasi |
|---|---|---|---|
| Token valid | Bearer token sah, scope benar | 200/2xx sesuai endpoint | Principal terbentuk, policy lolos |
| Tanpa token | Header tidak ada | 401 | Error code konsisten, tidak retry |
| Token expired | exp sudah lewat | 401 | Refresh dipicu bila relevan, tidak loop |
| Scope kurang | Token valid, scope kurang | 403 | Auth sukses, authorization gagal |
| Provider timeout | Dependency lambat/tidak respons | Status sesuai kebijakan sistem | Timeout, retry terbatas, metric tercatat |
| Provider 5xx | Introspeksi/JWKS gagal | Status sesuai kebijakan sistem | Tidak diterjemahkan salah menjadi 401 |
| Cache fallback | Provider gagal, cache token tersedia | Request diterima/ditolak sesuai policy | TTL aman, tidak melewati exp token |
| Rotasi kunci | Kunci lama tidak berlaku | Token diverifikasi dengan kunci baru | Refresh metadata berjalan benar |
Jika sistem dokumentasi Anda tidak nyaman dengan tabel HTML, ubah matriks ini menjadi checklist terstruktur di test plan atau file spesifikasi pipeline.
Checklist CI untuk verifikasi auth sebelum merge dan sebelum rilis
CI untuk auth tidak cukup menjalankan unit test biasa. Tambahkan gerbang verifikasi yang secara eksplisit menargetkan regresi saat dependensi bermasalah.
Checklist minimum di pipeline
- Jalankan unit test untuk validasi klaim, parsing token, dan pemetaan error.
- Jalankan integration test terhadap middleware, cache, dan policy internal.
- Jalankan contract test ke mock yang merepresentasikan provider eksternal.
- Jalankan subset contract verification ke sandbox/provider nyata bila tersedia.
- Simulasikan timeout, 401, 403, 429, dan 5xx dalam suite negatif.
- Verifikasi token expired dan refresh flow tanpa sleep nyata.
- Pastikan retry memiliki batas dan tidak aktif untuk 401/403.
- Pastikan log, metric, dan trace field penting muncul pada skenario gagal.
- Pastikan perubahan config timeout, audience, issuer, atau cache TTL ikut melalui review dan test.
- Blok merge bila ada flaky test auth yang belum di-triage; jangan biasakan rerun tanpa analisis.
Contoh workflow verifikasi sebelum rilis
1. Deploy ke staging dengan konfigurasi auth yang setara produksi
2. Jalankan smoke test auth untuk jalur 2xx, 401, dan 403
3. Jalankan fault injection terkontrol: timeout dan 5xx dari provider mock
4. Verifikasi dashboard: error rate auth, latency dependency, retry count
5. Verifikasi tidak ada lonjakan request ke provider akibat retry loop
6. Roll forward canary dengan traffic kecil
7. Bandingkan metrik auth canary vs baseline
8. Jika ada deviasi signifikan, rollback dan tahan rilisSinyal observability yang wajib dipantau
Tanpa observability yang tepat, regresi auth sering baru terlihat setelah pengguna mengeluh. Untuk sistem yang bergantung pada provider eksternal, pisahkan sinyal keamanan dari sinyal ketersediaan.
Metric
- Jumlah request auth sukses, 401, 403, 429, dan 5xx.
- Latency validasi token dan latency ke dependency auth.
- Timeout rate per dependency.
- Retry count dan retry-exhausted count.
- Cache hit/miss untuk token introspection atau metadata kunci.
- Refresh token success/failure rate.
Log
- Request correlation ID.
- Kategori hasil auth: invalid token, expired, forbidden, dependency timeout, dependency 5xx.
- Issuer/audience mismatch bila relevan, tanpa mencatat token mentah.
- Keputusan fallback dan alasan pemilihannya.
Trace
- Span khusus untuk validasi auth dan panggilan ke provider eksternal.
- Tag status dependency agar bottleneck terlihat jelas.
- Informasi retry attempt untuk membedakan satu kegagalan dari badai retry.
Hindari logging token penuh, refresh token, atau secret. Untuk debugging, cukup log fingerprint, subject, atau metadata nonrahasia yang sudah disanitasi.
Pola rollback dan verifikasi rollback sebelum rilis
Rollback pada perubahan auth lebih sensitif daripada fitur biasa karena terkait kompatibilitas token, kebijakan akses, dan konfigurasi provider. Rollback yang tidak diuji bisa memperburuk situasi, misalnya versi lama tidak mengenali klaim baru atau memakai endpoint metadata lama.
Yang perlu diverifikasi pada rollback
- Versi sebelumnya masih kompatibel dengan format token saat ini.
- Perubahan skema cache atau storage auth bersifat backward compatible atau memiliki migrasi aman.
- Perubahan audience, issuer, atau endpoint provider bisa dikembalikan tanpa merusak traffic aktif.
- Rotasi secret atau key tidak membuat rollback mustahil.
Pola yang membantu
- Feature flag untuk mengaktifkan validator baru tanpa deploy ulang.
- Dual-read atau dual-validate sementara saat migrasi kontrak auth, lalu bandingkan hasilnya.
- Canary release dengan pemantauan metrik auth terpisah dari metrik aplikasi umum.
- Pre-rollback test: uji singkat bahwa build sebelumnya masih bisa memproses token dan respons provider yang sedang aktif.
Pre-rollback test sering dilupakan. Padahal sebelum rilis, Anda sudah tahu versi rollback kandidatnya. Jalankan suite kecil terhadap artefak itu agar rollback bukan sekadar asumsi.
Kesalahan umum yang sering memicu regresi auth
- Menganggap semua error provider sebagai 401.
- Retry tanpa batas pada endpoint introspeksi atau refresh token.
- Mengandalkan sandbox eksternal di semua test sehingga pipeline tidak stabil.
- Membiarkan cache token hidup lebih lama dari masa berlaku token.
- Tidak menguji clock skew dan token expired secara deterministik.
- Mock hanya mencakup respons 200 sehingga perubahan kontrak error tidak terdeteksi.
- Tidak memisahkan auth failure karena security issue dari dependency failure karena availability issue.
Penutup
Strategi uji API auth untuk cegah regresi saat dependensi bermasalah harus dirancang untuk menghadapi kondisi gagal, bukan hanya kondisi normal. Kombinasi unit test yang ketat, integration test internal, contract test ke provider eksternal, simulasi timeout/401/403/5xx, observability yang jelas, dan verifikasi rollback akan jauh lebih efektif dibanding sekadar menambah E2E test.
Jika harus memilih prioritas, mulai dari tiga hal ini: bedakan jenis kegagalan auth dengan tepat, uji timeout dan token expired secara deterministik, lalu pastikan CI memblok perubahan yang merusak retry, fallback, atau kontrak integrasi. Dari sana, Anda bisa membangun workflow verifikasi yang lebih tahan terhadap gangguan dependensi tanpa mengorbankan keamanan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!