Gua inget banget pertama kali belajar bikin sistem login buat web app. Semua tutorial ngajarin hal yang sama: bikin REST API, pasang JWT, simpen token di localStorage. Keren, katanya. Stateless. Modern. Scalable.
Ternyata, itu salah.
Dan gua baru sadar setelah bertahun-tahun.
Artikel ini bukan buat nge-judge siapa pun yang masih pakai JWT — gua juga dulu pakai. Tapi setelah riset lebih dalam, ngobrol sama praktisi security, dan ngalamin sendiri migrasi dari JWT ke session cookies... gua rasa ini hal yang perlu lebih banyak developer tahu.
Kenapa Artikel Ini Penting?
JWT adalah salah satu tools yang paling banyak disalahgunakan dalam web development modern. Banyak developer (termasuk gua dulu) pakai JWT buat session login tanpa pernah baca spesifikasinya. Spesifikasi JWT sendiri bilang: token ini didesain buat umur pendek — sekitar 5 menit atau kurang. Bukan buat session login yang bisa bertahan berjam-jam atau berhari-hari.
Kabar baiknya: ada alternatif yang lebih simpel, lebih aman, dan udah terbukti puluhan tahun. Namanya session cookies. Dan kalau lu butuh token yang cryptographically signed dengan desain yang lebih aman, ada PASETO.
Masalah Utama JWT Buat Session
1. JWT Bukan Didisain Buat Session
Ini fakta yang paling sering diabaikan. Spesifikasi JWT (RFC 7519) mendeskripsikan token yang "singkat" dan "self-contained". Token ini ideal untuk skenario seperti Single Sign-On antar service, di mana token dikirim, diverifikasi, lalu langsung dihapus. Google pakai JWT bukan buat session browser — mereka pakai regular cookie sessions. JWT di Google dipakai murni sebagai transport SSO antar server.
Ketika lu pakai JWT sebagai session token yang bertahan 7 hari, lu udah keluar dari use case yang didesain. Dan di luar use case itu, celah keamanan mulai muncul.
2. Spesifikasi JOSE Itu Sendiri Bermasalah
JWT adalah bagian dari keluarga standar JOSE (Javascript Object Signing and Encryption). Masalahnya, standar ini punya cacat desain fundamental. Dua celah paling terkenal:
- Alg=none attack: Attacker bisa kirim header
"alg": "none"dan banyak library JWT (terutama versi lama) akan menerima token tanpa verifikasi signature sama sekali. Ini bukan bug implementasi — ini oleh-desain dari spesifikasinya. - Alg confusion attack: Attacker kirim header
"alg": "HS256"padahal server pakai RSA public key. Server akan memverifikasi token menggunakan RSA public key sebagai HMAC secret — yang mana public key itu... publik. Siapa pun bisa bikin token valid.
Scott Arciszewski dari Paragon Initiative Enterprises — salah satu tim security engineering paling dihormati di ekosistem open-source — menulis artikel pedas soal ini: "JOSE is a Bad Standard That Everyone Should Avoid." Dia bahkan bikin alternatifnya: PASETO.
3. "Stateless Sessions" Itu Mitos
Argumen favorit penggemar JWT: "Token-nya self-contained, jadi server nggak perlu simpan state. Lebih scalable."
Realitanya: lu tetap butuh state.
- Butuh revoke token? Lu harus simpan blacklist. Itu state.
- Butuh refresh token? Lu harus simpan refresh token. Itu state.
- Butuh tau session mana yang aktif? Lu harus simpan daftar session. Itu state.
- Butuh update role user tanpa bikin dia login ulang? State lagi.
Pada akhirnya, aplikasi yang "stateless" cuma ilusi. Dan begitu lu harus maintain state, JWT kehilangan satu-satunya keunggulan yang diklaim. Sekarang lu punya kompleksitas JWT plus kompleksitas state management.
4. localStorage Itu Tempat yang Buruk Buat Simpan Token
Banyak tutorial ngajarin simpan JWT di localStorage. Randall Degges, Developer Advocate yang udah bertahun-tahun ngomongin topik ini, tegas: jangan simpan kredensial autentikasi di localStorage.
Alasannya simpel: localStorage bisa diakses JavaScript apa pun yang jalan di domain yang sama. Kalau ada XSS — dan XSS adalah vulnerability paling umum di web — attacker langsung bisa baca token lu. Sementara httpOnly cookie nggak bisa disentuh JavaScript sama sekali.
Balik ke Session Cookies: Solusi yang Simpel dan Udah Terbukti
Session cookies udah dipakai sejak web ada. Caranya simpel:
- User login → server buat session ID random (cryptographically secure).
- Session ID dikirim sebagai httpOnly, secure, sameSite cookie.
- Data session disimpan di server (Redis, database, memory — tergantung skala).
- Setiap request, server cek session ID dari cookie, ambil data dari store.
Keunggulan dibanding JWT:
- Lebih aman: Cookie httpOnly nggak bisa diakses JavaScript. Zero XSS risk buat token.
- Revocation instan: Hapus session dari store, user langsung logout. Nggak perlu blacklist token.
- Lebih simpel: Nggak perlu handle refresh token, token expiry, token rotation, atau blacklist.
- Data aman: Data session nggak pernah dikirim ke client. Berbeda dengan JWT yang payload-nya cuma base64-encoded (bukan encrypted).
- Ukuran lebih kecil: Cookie cuma 32–64 byte random string. JWT bisa ratusan byte bahkan dengan payload minimal.
Satu-satunya "kelemahan" yang sering disebut: perlu shared session store (Redis) untuk horizontal scaling. Tapi ini masalah yang udah solved sejak 2010. Dan scaling sampe butuh multi-region Redis itu masalah yang hanya dialami <0.1% aplikasi.
PASETO: Kalau Lu Tetap Butuh Token Kriptografis
Ada skenario di mana lu emang butuh token cryptographically signed — misalnya untuk komunikasi antar service, API key untuk third-party, atau one-time action links (reset password, email verification).
Untuk use case itu, jangan pakai JWT. Pakai PASETO.
PASETO (Platform-Agnostic SEcurity TOkens) didesain oleh Scott Arciszewski — orang yang sama yang nulis artikel "JOSE is Bad" di atas. Desainnya menghilangkan semua kelemahan JWT:
- Nggak ada header
algyang bisa dimanipulasi — setiap versi PASETO punya algoritma tetap. - Nggak ada opsi
none. - Payload dienkripsi (untuk
localtokens) atau cuma signed (untukpublictokens), tapi selalu jelas. - Implementasi tersedia di 20+ bahasa pemrograman.
Pengalaman Gua Migrasi dari JWT ke Session
Di proyek Nuxt 4 sehari-hari gua, dulu semua endpoint auth pakai JWT. Setup-nya kira-kira begini:
- Login → server return access token (15 menit) + refresh token (7 hari).
- Client simpan di cookie (bukan localStorage, untungnya).
- Setiap request, kirim access token via Authorization header.
- Kalau expired, pake refresh token buat dapet access token baru.
- Kalau refresh token expired, user harus login lagi.
Terlihat rapi di atas kertas. Tapi realitanya:
- Refresh token rotation bikin kompleksitas ekstra. Ada race condition kalau user buka banyak tab.
- Revoke session manual itu ribet — harus maintain token blacklist atau version counter di database.
- Debugging auth issue selalu dimulai dari "coba clear token dulu."
- Payload JWT makin gede karena makin banyak klaim yang dimasukin.
Setelah ganti ke session cookies:
- Setup auth berkurang dari ~200 baris jadi ~50 baris.
- Revoke session tinggal
DELETE FROM sessions WHERE user_id = ?. - Nggak ada lagi "coba clear token, refresh page."
- Dashboard admin bisa lihat semua session aktif per user — fitur yang hampir mustahil rapi dengan pure JWT.
Bukan berarti JWT jelek untuk semua hal. Tapi untuk session login user di web app? Session cookies menang telak.
Kapan JWT Masih Boleh Dipakai?
Supaya adil, ada use case di mana JWT masih oke:
- Single Sign-On transport antar service — ini use case asli JWT.
- One-time token untuk reset password atau verifikasi email (meskipun PASETO lebih baik).
- API key untuk third-party yang butuh self-contained claims.
- Komunikasi antar microservices di internal network yang trusted.
Aturan utama: kalau token itu hidup lebih dari 5 menit dan berkaitan dengan session user, jangan pakai JWT.
Langkah Praktis Kalau Lu Mau Migrasi
Kalau lu udah terlanjur pakai JWT dan mau pindah ke session, ini pendekatan yang gua pakai:
- Jangan rewrite sekaligus. Bikin endpoint auth baru dengan session, biarin endpoint JWT lama tetap jalan.
- Pakai library session yang mature. Di Node.js:
express-session+ Redis store. Di Nuxt:nuxt-auth-utils. Di Go:gorilla/sessions. Di Python: Flask bawaan udah ada session. - Set cookie flags yang benar:
httpOnly=true,secure=true,sameSite=Lax. - Redirect user bertahap. Deteksi kalau user masih pakai JWT token lama, redirect ke endpoint refresh yang ngasih session cookie baru.
- Setelah masa transisi, matikan endpoint JWT.
Intinya
JWT bukan malicious technology. Tapi dia adalah tools yang sering dipakai di luar konteks desainnya — dan itu bikin lubang keamanan yang nggak perlu.
Kalau lu bikin web app dengan user login: pakai session cookies.
Kalau lu butuh token kriptografis antar service: pakai PASETO.
Dan kalau tutorial berikutnya yang lu tonton mulai dengan "install jsonwebtoken" buat sistem login... lu udah tahu harus skeptis.
Referensi / Bacaan Lanjut
- Stop using JWTs! — samsch. Rangkuman komprehensif kenapa JWT nggak cocok buat session.
- Stop using JWT for sessions — joepie91. Artikel klasik 2016 yang membongkar mitos "stateless JWT sessions."
- JOSE is a Bad Standard That Everyone Should Avoid — Paragon Initiative Enterprises. Analisis teknis mendalam soal cacat desain standar JOSE.
- PASETO: Platform-Agnostic Security Tokens — Alternatif yang lebih aman dari JWT, didesain oleh Scott Arciszewski.
- Please Stop Using Local Storage — Randall Degges (YouTube). Presentasi kenapa localStorage bukan tempat buat kredensial.
- HN Discussion: Stop Using JWTs (June 2026) — 449 points, 263 komentar. Diskusi terbaru soal topik ini.