Checklist Bug Smash untuk hardening auth dan session API berguna saat tim ingin mengaudit titik lemah yang sering lolos code review, tetapi punya dampak besar ke pengambilalihan akun, pembajakan sesi, dan abuse otomatis. Jika Anda perlu titik mulai yang praktis: periksa regenerasi session setelah login, kebijakan expiry access/refresh token, rotasi refresh token, cookie flags, sanitasi log, validasi input login/reset password, dan kontrol brute force serta rate limit per jalur sensitif.
Artikel ini bukan daftar teori keamanan umum. Fokusnya adalah panduan eksekusi untuk backend engineer: apa yang harus dicek, bagaimana mengurutkan prioritas severity, seperti apa implementasi aman, anti-pattern yang sering terlihat di API production, dan verifikasi apa saja yang wajib lulus sebelum rilis.
Mental model bug-smash: audit cepat, temukan blast radius, perbaiki yang bisa dieksploitasi dulu
Semangat bug-smash berarti mencari bug dengan disiplin triase, bukan hanya mengumpulkan temuan. Pada auth dan session API, urutan prioritas biasanya didorong oleh dua pertanyaan:
- Bisakah penyerang mengambil alih akun atau sesi?
- Bisakah penyerang melakukan abuse secara massal dengan biaya rendah?
Dari sana, severity praktis bisa disusun seperti berikut:
- Kritis: session fixation yang memungkinkan reuse session ID, refresh token tidak diputar dan bisa dipakai berulang, token tanpa expiry yang efektif permanen, reset password flow yang bisa ditebak/diabuse, secret/token bocor ke log yang dapat diakses banyak pihak.
- Tinggi: cookie tanpa
HttpOnly/Securepada konteks yang semestinya, rate limit lemah di login/reset/refresh, validasi input yang memungkinkan account enumeration atau bypass kontrol. - Menengah: konfigurasi expiry terlalu longgar tanpa kompensasi monitoring/device binding, logging error terlalu verbose, notifikasi keamanan tidak ada.
- Rendah: inkonsistensi response, naming endpoint yang membocorkan detail internal, atau header keamanan yang tidak relevan langsung ke auth namun tetap perlu dirapikan.
Prinsip praktis: perbaiki dulu bug yang mengubah auth API dari “perlu interaksi pengguna” menjadi “bisa dieksploitasi otomatis” atau dari “butuh satu token” menjadi “token yang bocor berlaku lama dan sulit dicabut”.
Checklist audit auth dan session API
1. Session fixation
Masalahnya: penyerang membuat atau mengetahui session ID sebelum korban login, lalu korban login menggunakan session yang sama. Jika server tidak meregenerasi session setelah autentikasi, penyerang dapat memakai session tersebut untuk mengakses akun korban.
Yang harus dicek:
- Apakah session ID selalu diregenerasi setelah login berhasil?
- Apakah session lama diinvalidasi setelah privilege berubah, misalnya dari anonymous menjadi authenticated?
- Apakah logout menghapus state session server-side, bukan hanya menghapus cookie di client?
- Apakah ada endpoint “remember device” atau “elevate privilege” yang juga perlu regenerasi session?
Implementasi aman:
- Regenerasi session ID setelah login sukses.
- Invalidasi session sebelumnya saat diperlukan, terutama untuk flow sensitif.
- Hindari menempelkan identitas user hanya ke session yang sudah ada tanpa rotasi identifier.
Anti-pattern:
- Login endpoint hanya menambah
user_idke session existing. - Middleware auth menganggap session cookie yang sama aman sebelum dan sesudah login.
- Logout hanya menghapus cookie browser tetapi record session tetap valid di backend atau store terdistribusi.
2. Expiry token terlalu lemah
Masalahnya: access token atau session berlaku terlalu lama, atau tidak punya batas waktu efektif. Semakin lama masa berlaku token, semakin besar dampak kebocoran melalui XSS, log, browser extension, perangkat hilang, atau reverse proxy yang salah konfigurasi.
Yang harus dicek:
- Apakah access token punya expiry pendek dan terukur?
- Apakah refresh token punya expiry absolut, bukan hanya inactivity timeout?
- Apakah ada mekanisme pencabutan token saat logout, reset password, atau compromise terdeteksi?
- Apakah server benar-benar memvalidasi
exp,nbf, dan status token, bukan sekadar signature?
Praktik aman:
- Gunakan access token berumur pendek untuk meminimalkan blast radius.
- Gunakan refresh token berumur lebih panjang tetapi diawasi dan bisa dicabut.
- Terapkan batas absolut lifetime refresh token agar sesi tidak hidup selamanya melalui refresh berantai.
Trade-off: expiry yang terlalu pendek menambah frekuensi refresh dan kompleksitas sinkronisasi client. Solusinya bukan memperpanjang token tanpa batas, melainkan membuat flow refresh stabil dan terobservasi.
3. Rotasi refresh token
Masalahnya: jika refresh token bisa dipakai berulang tanpa rotasi, token yang dicuri dapat digunakan terus-menerus selama masih berlaku. Ini sangat berbahaya karena sering luput dari pengujian fungsional biasa.
Yang harus dicek:
- Apakah refresh token diganti setiap kali dipakai?
- Apakah refresh token lama langsung dicabut setelah rotasi berhasil?
- Apakah reuse refresh token lama dianggap sinyal compromise?
- Apakah keluarga token (token family) bisa dicabut jika reuse terdeteksi?
Implementasi aman: simpan refresh token secara terhash di server, hubungkan ke satu keluarga token, dan pada setiap refresh lakukan:
- Validasi token dan statusnya.
- Tandai token lama sebagai sudah dipakai/dicabut.
- Terbitkan refresh token baru dan access token baru.
- Jika token lama dipakai lagi, anggap reuse dan cabut sesi terkait.
// Pseudocode alur refresh token yang aman
function refresh(refreshTokenRaw) {
const tokenHash = hash(refreshTokenRaw)
const token = db.findRefreshToken(tokenHash)
if (!token || token.revoked || token.used || token.expired) {
deny("invalid_refresh_token")
}
beginTransaction()
markUsed(token.id)
const newRefresh = randomSecret()
const newHash = hash(newRefresh)
insertRefreshToken({
familyId: token.familyId,
parentId: token.id,
tokenHash: newHash,
expiresAt: nextRefreshExpiry()
})
const accessToken = issueAccessToken(token.userId)
commit()
return { accessToken, refreshToken: newRefresh }
}
Anti-pattern:
- Menyimpan refresh token mentah di database.
- Refresh endpoint mengembalikan access token baru tanpa mengganti refresh token.
- Logout hanya menghapus token di client, tetapi refresh token tetap valid di backend.
4. Cookie flags dan transport
Jika auth berbasis cookie, salah konfigurasi cookie sering menjadi celah langsung.
Yang harus dicek:
HttpOnlyuntuk mencegah akses JavaScript ke cookie sesi.Secureagar cookie hanya terkirim melalui HTTPS.SameSitedisetel sesuai kebutuhan untuk menekan CSRF.PathdanDomaintidak terlalu luas.- Tidak ada fallback ke HTTP pada environment production.
Catatan penting: SameSite membantu, tetapi bukan pengganti total proteksi CSRF untuk aplikasi yang bergantung pada cookie otomatis. Jika endpoint sensitif menerima cookie browser, pertimbangkan token CSRF atau strategi anti-CSRF lain yang sesuai arsitektur Anda.
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/Anti-pattern:
- Menyimpan access token di
localStoragetanpa alasan arsitektural yang kuat. - Menggunakan cookie auth lintas subdomain secara luas padahal hanya satu host yang butuh.
- Mematikan
Securedemi kompatibilitas environment yang salah konfigurasi.
5. Secret leakage di log
Masalahnya: token, authorization header, OTP, link reset password, atau secret internal tercatat di log aplikasi, reverse proxy, tracing, atau error collector. Begitu data ini tersimpan, blast radius-nya melebar ke banyak sistem dan banyak orang.
Yang harus dicek:
- Apakah request/response body auth dicatat mentah?
- Apakah header
Authorization, cookie, dan token reset disamarkan? - Apakah exception handler menuliskan payload sensitif saat validasi gagal?
- Apakah debug mode, access log, APM, dan tracing punya aturan redaksi field sensitif?
Praktik aman:
- Redact field sensitif sebelum logging.
- Jangan log token penuh; bila perlu log sebagian kecil fingerprint yang tidak bisa dipakai ulang.
- Pisahkan audit event dari debug log biasa.
// Contoh redaksi sederhana sebelum logging
function sanitizeAuthLog(input) {
return {
...input,
password: input.password ? "[REDACTED]" : undefined,
token: input.token ? "[REDACTED]" : undefined,
authorization: input.authorization ? "[REDACTED]" : undefined,
refresh_token: input.refresh_token ? "[REDACTED]" : undefined
}
}
Anti-pattern:
logger.info(req.headers, req.body)pada endpoint login/reset.- Menaruh link reset password lengkap ke job log atau email debug preview yang bisa diakses internal secara luas.
- Menampilkan secret di response error non-production lalu tanpa sengaja terbawa ke production.
6. Validasi input login dan reset password
Validasi input pada auth bukan hanya soal format. Tujuannya adalah mencegah abuse, enumeration, payload aneh, dan jalur error yang tak terduga.
Yang harus dicek:
- Apakah field login divalidasi ketat sesuai tipe yang diharapkan?
- Apakah reset password token diverifikasi dengan panjang/format yang tepat sebelum query ke database?
- Apakah response untuk user tidak ditemukan dibedakan dari password salah?
- Apakah endpoint reset password memiliki expiry dan single use token?
Praktik aman:
- Gunakan response generik untuk login gagal dan permintaan reset password.
- Normalisasi input yang memang perlu dinormalisasi, misalnya email.
- Hindari membocorkan status akun seperti “email tidak terdaftar”, “akun nonaktif”, atau “token reset valid tetapi kadaluarsa” ke aktor yang belum terautentikasi.
Anti-pattern:
- Membedakan response antara email salah dan password salah.
- Token reset berupa nilai pendek atau dapat ditebak.
- Link reset password tidak invalid setelah dipakai sekali.
7. Brute force, rate limit, dan abuse prevention
Endpoint auth adalah target otomatisasi. Tanpa kontrol abuse, kredensial stuffing, brute force OTP, dan pembanjiran refresh/login dapat dilakukan dengan murah.
Yang harus dicek:
- Apakah login, refresh token, forgot password, verify OTP, dan resend code memiliki rate limit sendiri?
- Apakah pembatasan diterapkan per IP, per akun, dan bila perlu per device fingerprint atau subnet?
- Apakah ada progressive delay, lock sementara, atau challenge tambahan setelah anomali?
- Apakah reset password dan signup dilindungi dari spam massal?
Pendekatan yang umum dipakai:
- Per IP: baik untuk menahan serangan kasar dari sumber tunggal, tetapi lemah untuk botnet dan bisa menyakiti pengguna di NAT bersama.
- Per akun/identifier: baik untuk menahan serangan pada satu user, tetapi perlu hati-hati agar tidak jadi alat denial-of-service terhadap akun target.
- Hybrid: kombinasi IP, akun, dan pola perilaku biasanya paling realistis.
Contoh kebijakan praktis:
- Login: rate limit per IP dan per identifier.
- Forgot password: rate limit per email/nomor dan per IP, dengan response generik.
- Refresh token: rate limit per sesi/per token family untuk mendeteksi loop client atau abuse.
- OTP verify/resend: batasi percobaan dan frekuensi pengiriman.
// Pseudocode rate limit hybrid untuk login
keyIp = "login:ip:" + clientIp
keyUser = "login:user:" + normalize(loginIdentifier)
if (tooManyRequests(keyIp) || tooManyRequests(keyUser)) {
deny("too_many_attempts")
}
Anti-pattern:
- Rate limit hanya pada API gateway, tetapi endpoint internal bypass gateway.
- Hanya membatasi per IP sehingga attack terdistribusi lolos.
- Men-lock akun permanen setelah beberapa percobaan gagal tanpa jalur recovery yang aman.
Contoh alur audit: dari endpoint map ke bukti eksploitasi
Audit yang efektif biasanya dimulai dari inventaris endpoint dan state transition, bukan dari membaca source code secara acak.
Langkah 1: petakan semua endpoint auth
POST /loginPOST /logoutPOST /refreshPOST /forgot-passwordPOST /reset-passwordPOST /verify-otpPOST /resend-otpGET /sessionatau endpoint introspeksi serupa
Catat media auth yang dipakai: cookie, bearer token, session server-side, atau kombinasi.
Langkah 2: telusuri state transition
Untuk tiap endpoint, jawab pertanyaan berikut:
- Apa input sensitifnya?
- Apa output sensitifnya?
- Apa state yang berubah di server?
- Bagaimana token/session dibuat, disimpan, diputar, dan dicabut?
- Apa yang terjadi jika request diulang, terlambat, atau diparalelkan?
Langkah 3: uji abuse case, bukan hanya happy path
- Coba login berkali-kali dengan identifier sama.
- Coba pakai refresh token yang sama dua kali, termasuk request paralel.
- Cek apakah session ID berubah setelah login.
- Cek apakah logout benar-benar mencabut sesi/token.
- Uji reset password token: reuse, expired, format invalid, enumeration.
- Periksa log aplikasi/APM apakah secret ikut tercatat.
Langkah 4: kumpulkan bukti yang bisa ditindaklanjuti
Temuan yang bagus bukan “rate limit kurang ketat”, tetapi misalnya: refresh token yang sama dapat dipakai dua kali dalam jendela race condition sehingga dua token aktif diterbitkan; replay berikutnya tidak menandai compromise. Bukti seperti ini langsung mengarah ke perubahan desain atau transaksi database.
Contoh implementasi aman yang layak diprioritaskan
Regenerasi session setelah login
// Pseudocode generik
if (credentialsValid(user, password)) {
session.regenerateId()
session.set("user_id", user.id)
session.set("auth_time", now())
}
Kenapa ini bekerja: identitas sesi setelah login tidak lagi terikat pada identifier lama yang mungkin sudah diketahui pihak lain.
Hash refresh token di database
Perlakukan refresh token seperti secret. Jika database bocor, token mentah tidak boleh langsung bisa dipakai. Hash juga membantu membatasi dampak kebocoran akses baca internal.
Single-use token untuk reset password
Reset token harus acak, berumur pendek, dan invalid setelah dipakai sekali. Setelah reset berhasil, pertimbangkan mencabut sesi aktif lain atau minimal memaksa login ulang pada konteks berisiko.
Response generik untuk endpoint sensitif
Pada forgot password, gunakan respons seperti: Jika akun ada, instruksi akan dikirim. Ini menekan account enumeration tanpa mengubah UX inti.
Observability yang aman
Tambahkan audit event seperti login success/failure, refresh success/reuse detected, logout, reset password requested/completed. Simpan metadata yang berguna untuk investigasi, tetapi jangan simpan secret mentah.
Anti-pattern yang sering lolos code review
- “JWT sudah signed, berarti aman.” Signature tidak menyelesaikan masalah expiry, revocation, storage, atau reuse refresh token.
- Cookie sudah
HttpOnly, berarti CSRF selesai. Tidak selalu. Jika browser otomatis mengirim cookie, Anda tetap harus menilai risiko CSRF sesuai flow. - Logout cukup hapus token di frontend. Salah jika backend masih menerima token atau refresh token lama.
- Rate limit ada di edge, jadi aplikasi aman. Endpoint internal, job worker, atau jalur bypass sering terlupakan.
- Debug log hanya sementara. Sementara sering berubah menjadi permanen dan ikut masuk ke sistem logging terpusat.
- Validasi format token cukup. Format valid tidak berarti token belum dicabut, belum dipakai, atau masih dalam token family yang sah.
Debugging tips saat memperbaiki bug auth/session
Masalah race condition pada refresh token
Jika dua request refresh paralel sama-sama berhasil, cek:
- Apakah pembacaan dan penandaan token lama terjadi dalam satu transaksi?
- Apakah ada unique constraint atau status state machine yang mencegah double-use?
- Apakah worker atau instance aplikasi berbeda melihat state yang konsisten?
Rate limit terasa tidak efektif
Periksa:
- Apakah key limiter konsisten setelah normalisasi identifier?
- Apakah reverse proxy mengirim IP client yang benar?
- Apakah endpoint tertentu tidak melewati middleware limiter?
- Apakah cache/store rate limit cukup konsisten untuk deployment multi-instance?
Session fixation belum tertutup
Periksa bukan hanya login utama, tetapi juga flow SSO callback, magic link, verifikasi email yang otomatis login, dan privilege escalation ke area admin.
Checklist verifikasi sebelum rilis
- Session ID diregenerasi setelah login dan perubahan privilege penting.
- Logout mencabut sesi/token di backend, bukan hanya membersihkan client state.
- Access token punya expiry pendek dan tervalidasi server-side.
- Refresh token punya expiry, rotasi, single-use semantics, dan deteksi reuse.
- Refresh token disimpan terhash, bukan mentah.
- Cookie auth memakai
HttpOnly,Secure, danSameSiteyang sesuai. - Cookie
DomaindanPathtidak terlalu luas. - Header
Authorization, cookie, password, OTP, dan token reset tidak tercatat mentah di log. - Endpoint login, refresh, forgot password, verify OTP, resend OTP, dan reset password punya rate limit.
- Rate limit minimal diterapkan secara hybrid: per IP dan per akun/identifier bila relevan.
- Response error tidak membocorkan apakah akun ada, password salah, atau token reset milik siapa.
- Reset password token acak, berumur pendek, dan single-use.
- Uji replay untuk refresh token dan reset password token sudah dilakukan.
- Uji request paralel pada refresh endpoint sudah dilakukan.
- Audit event tersedia untuk login gagal, reuse refresh token, reset password, dan logout.
- Dokumentasi incident response menjelaskan cara mencabut sesi/token bila compromise terdeteksi.
Penutup
Hardening auth dan session API jarang gagal karena satu bug spektakuler. Lebih sering, masalah muncul dari beberapa keputusan kecil yang tampak masuk akal: expiry terlalu panjang, refresh token tidak diputar, cookie longgar, log terlalu verbose, dan endpoint sensitif tanpa pembatasan abuse. Dengan pendekatan checklist bug smash, tim backend bisa mengubah audit keamanan dari aktivitas ad-hoc menjadi rangkaian verifikasi yang jelas, dapat diulang, dan langsung bisa dieksekusi sebelum rilis.
Jika harus memilih tiga perbaikan paling berdampak, mulai dari: regenerasi session setelah login, rotasi refresh token dengan deteksi reuse, dan sanitasi log + rate limit pada seluruh endpoint auth sensitif. Tiga area ini sering memberi penurunan risiko terbesar dengan perubahan arsitektur yang masih realistis untuk dilakukan cepat.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!