Di episode #10 saya sempat bertanya-tanya: kalau browser terlalu sopan mengirim cookie, kenapa kita masih memakainya? Jawabannya ada di episode #11 ini — cookie session dan JWT itu dua pendekatan berbeda untuk satu masalah yang sama: bagaimana server tahu “kamu siapa” di request berikutnya, mengingat HTTP sendiri tidak punya memori. Bagian yang paling saya sesali sebagai programmer: dulu saya kira JWT otomatis lebih aman karena “ada tanda tangan kriptografisnya”. Ternyata tidak otomatis — justru kesalahan pakai JWT sangat sering.
HTTP itu amnesia, dan sesi adalah cara mengakalinya
HTTP bersifat stateless: setiap request diperlakukan sebagai kejadian baru, seolah tidak pernah ada request sebelumnya. Ketika saya login ke sebuah aplikasi, server perlu menyimpan fakta “user ini sudah login” supaya halaman berikutnya tidak meminta login ulang. Tempat penyimpanan fakta itu bisa di server (session) atau dibawa sendiri oleh klien (token). Analogi sederhananya: session = saya menitipkan kartu parkir di loket, dan loket menyimpan catatannya; token = loket menerbitkan tiket ber-stempel yang saya bawa sendiri di saku.
Session cookie: klasik, server-side, mudah dicabut
Pada pendekatan session, saat login berhasil server membuat identifier acak — session ID — lalu menyimpan state user di database atau memori server (dengan session ID sebagai kuncinya). Identifier itu dikirim ke browser lewat cookie. Di request berikutnya browser mengirim cookie itu kembali otomatis, dan server tinggal mencari catatannya.
Menurut OWASP Session Management Cheat Sheet, session ID minimal harus punya 64 bit entropy supaya tidak bisa ditebak. OWASP juga menghitung: dengan 64 bit entropy dan 100.000 session aktif, penyerang yang menebak 10.000 kali per detik butuh sekitar 585 tahun. Angka itu yang meyakinkan saya bahwa random identifier yang benar itu murah dan sangat efektif — masalah muncul ketika developer membuat session ID sendiri pakai rand() atau timestamp, bukan CSPRNG.
Kekuatan utama session: server adalah pemilik state. Saat user logout, atau admin mencabut akses, server cukup menghapus record session — selesai dalam satu detik. Kelemahannya: untuk aplikasi multi-server (microservice, load balancer), server harus berbagi store session (Redis, database), dan mulai terasa rumit ketika ada banyak service yang perlu memvalidasi session yang sama.
JWT: stateless, portable, tapi setiap keputusan ada harganya
JWT (JSON Web Token) membalik logikanya: state tidak disimpan di server, tapi dibawa dalam token itu sendiri. Token terdiri dari tiga bagian yang di-encode base64url dan dipisah titik: header (algoritma & tipe token), payload (klaim-klaim seperti user ID, role, expiry), dan signature (tanda tangan kriptografis). Server tidak perlu menyimpan apa pun — cukup memverifikasi signature dengan kunci yang diketahui, lalu membaca payload.
Saya membandingkan keduanya dalam tabel ini supaya keputusan saya tidak lagi berdasar “kedengerannya lebih modern”:
| Aspek | Session Cookie | JWT |
|---|---|---|
| Tempat state | Server (DB/Redis/memori) | Di dalam token (klien bawa) |
| Skala multi-server | Butuh shared store | Tanpa shared store |
| Pencabutan (logout/revoke) | Instan — hapus record | Sulit — token masih valid sampai expired |
| Ukuran tiap request | Kecil (session ID saja) | Besar (payload ikut terkirim) |
| Risiko khas | Session fixation, hijacking | Salah verifikasi signature, token dicuri |
Kesalahan umum pakai JWT yang saya temukan
Berdasarkan OWASP JWT Cheat Sheet, ada beberapa jebakan yang ternyata lumrah terjadi:
- Algoritma
none. Spesifikasi JWT mengenal algoritma “none” (tanpa signature). Kalau library diminta menerima semua algoritma, penyerang cukup mengubah header jadi"alg":"none, mengosongkan signature, dan token diterima begitu saja. - Key confusion (RS256 → HS256). Kalau server memakai RSA (asymmetric), kunci publik bersifat terbuka. Penyerang mengubah algoritma jadi HS256 (symmetric) lalu menandatangani token memakai kunci publik server itu sendiri — dan verifikasi lolos karena library memakai kunci yang “sama”.
- Tidak memeriksa expiry. Klaim
expada di payload, tapi kalau tidak dicek secara eksplisit, token lama tetap dianggap sah selamanya. - Secret lemah / bocor lewat git. Sama seperti masalah secrets di episode #10 — JWT secret yang kuat tapi tersimpan di repo publik tidak ada gunanya.
Bentuk kesalahan fatal yang umum (penulis dulu pernah menulis kode begini, jadi saya catat untuk pengingat):
// VULNERABLE: menerima semua algoritma + tidak cek expiry
const decoded = jwt.verify(token); // tanpa options
// decoded.payload dipercaya apa adanya oleh server
// AMAN: pin algoritma secara eksplisit + cek expiry
const decoded = jwt.verify(token, PUBLIC_KEY, {
algorithms: ['RS256'], // hanya RS256, tolak 'none' & HS256
ignoreExpiration: false // default true di beberapa library lama
});Prinsipnya: jangan pernah mempercayai header alg dari token — tentukan algoritma yang diharapkan di sisi server, bukan dari token yang datang.
OAuth 2.0 & OpenID Connect: otorisasi vs identitas
Di sini saya sempat salah paham cukup lama. OAuth 2.0 sebenarnya bukan protokol login — ia protokol otorisasi: bagaimana user memberi aplikasi pihak ketiga izin terbatas mengakses resource-nya (misal: akses daftar file di drive) tanpa membagikan password. Protokol identitas — “user ini siapa” — ditambahkan di lapisan atas bernama OpenID Connect (OIDC), yang menerbitkan ID token (berisi klaim identitas) di samping access token (untuk mengakses API).
Dokumen IETF RFC 9700 “Best Current Practice for OAuth 2.0 Security” (diterbitkan Januari 2025) merangkum pelajaran implementasi selama bertahun-tahun dan secara formal mendepresiasi Implicit flow dan Password grant. Rekomendasi utamanya: PKCE (Proof Key for Code Exchange) diwajibkan untuk semua Authorization Code flow — termasuk yang berjalan di server — untuk mencegah code interception. Bagi saya ini penanda penting: cara lama yang dulu “boleh” sekarang sudah resmi tidak boleh.
Yang saya terapkan mulai sekarang
Untuk aplikasi internal sederhana dengan satu server, saya tetap memilih session cookie — sederhana, mudah dicabut, dan framework PHP/Laravel sudah menanganinya dengan benar selama konfigurasi cookie-nya tepat (HttpOnly, Secure, SameSite — dibahas di episode #9 dan #10). JWT saya pakai ketika memang butuh stateless antar service, dan dengan tiga kebiasaan wajib: pin algoritma, cek expiry, dan simpan secret di environment/secret manager — bukan di repo.
Dan satu pelajaran non-teknis: hampir semua “login dengan Google/GitHub” yang saya lihat di produksi memakai OAuth 2.0 + OIDC di belakangnya. Memahami alurnya membuat saya berhenti asal klik tombol OAuth dan mulai melihat redirect URI, scope, dan token yang diminta — ini justru bahan utama untuk episode #10 tentang CSRF, dan bekal untuk topik API security nanti.
Sumber
Daftar rujukan yang saya pakai untuk catatan ini:
- OWASP Cheat Sheet Series — Session Management (entropi session ID, atribut cookie)
- OWASP Cheat Sheet Series — JSON Web Token (kesalahan umum JWT & mitigasi)
- RFC 9700 — Best Current Practice for OAuth 2.0 Security (IETF, Januari 2025; deprecasi Implicit flow & Password grant, mandat PKCE)
- OAuth 2.0 Security Best Current Practice — ringkasan resmi oauth.net
- OpenID Connect Core 1.0 — spesifikasi resmi openid.net
- RFC 7519 — JSON Web Token (JWT) (IETF)
Komentar Terbaru