Session vs JWT

Session berbasis cookie dan token JWT beserta kelebihan-kekurangannya.

Daftar isi

Autentikasi pada aplikasi web memerlukan cara untuk mengingat pengguna yang sudah masuk setelah halaman atau permintaan berikutnya dikirim. Topik ini membahas dua pendekatan utama, yaitu session berbasis cookie yang disimpan di sisi server dan token JWT yang disimpan di sisi klien dan diverifikasi lewat tanda tangan. Setelah membaca topik ini, perbedaan mekanisme kedua pendekatan dapat dijelaskan, token JWT dapat ditandatangani dan diverifikasi dengan pustaka jsonwebtoken, serta kelebihan dan kekurangan masing-masing dapat dipertimbangkan saat memilih strategi autentikasi.

Session adalah rekaman state pengguna yang disimpan di sisi server. Saat pengguna berhasil masuk, server membuat sebuah pengenal unik (biasanya bernama sid atau session_id), menempatkannya di dalam penyimpanan (memori proses, Redis, atau basis data), lalu mengirimkan pengenal itu kembali ke browser lewat header Set-Cookie. Pada permintaan berikutnya, browser secara otomatis menyertakan cookie itu lewat header Cookie, dan server mencari pengenal tersebut pada penyimpanan untuk memuat data pengguna.

Pola ini bersifat stateful: server menyimpan data yang mengikat pengenal ke pengguna. Bila pengguna ingin keluar, server menghapus entri pengenal dari penyimpanan sehingga cookie yang masih ada di browser tidak lagi berlaku. Bila server dijalankan pada lebih dari satu instance, penyimpanan harus dibagi (misalnya Redis) agar session yang dibuat pada instance A tetap dapat dipakai pada instance B.

Mekanisme token JWT#

JWT (JSON Web Token) adalah token berbentuk string yang terdiri atas tiga bagian dipisahkan titik: header, payload, dan tanda tangan. Header menyatakan algoritma tanda tangan, payload memuat klaim seperti sub (subjek pengguna) dan peran, sedangkan tanda tangan dihitung dari header dan payload dengan sebuah kunci rahasia yang hanya diketahui server. Karena tanda tangan melindungi integritas token, server dapat mempercayai isi payload tanpa menyimpannya.

Token JWT dikirim ke klien pada respons login, biasanya di dalam body JSON. Klien kemudian menyimpan token (misalnya di localStorage atau sessionStorage) dan menyertakannya pada setiap permintaan berikutnya lewat header Authorization: Bearer <token>. Server menerima token, memverifikasi tanda tangannya dengan kunci rahasia, dan membaca payload untuk mengenali pengguna.

js jwt-demo.js
const jwt = require("jsonwebtoken");

const RAHASIA = "rahasia-latihan-tanda-tangan-token";
const muatan = { sub: "usr_123", nama: "Dewi", peran: "editor" };

// Tanda tangan tanpa iat agar token dapat direproduksi pada demo ini.
// Pada produksi, expiresIn: "1h" dipakai sehingga iat dan exp ditambahkan.
const token = jwt.sign(muatan, RAHASIA, { noTimestamp: true });
console.log("token:", token);

const terurai = jwt.verify(token, RAHASIA);
console.log("payload:", JSON.stringify(terurai));

const lengkap = jwt.decode(token, { complete: true });
console.log("header:", JSON.stringify(lengkap.header));

// Token kedaluwarsa dimodelkan dengan expiresIn negatif. Token tidak dicetak
// agar keluaran demo tetap dapat direproduksi, hanya pesan error yang ditampilkan.
const tokenKadaluarsa = jwt.sign(muatan, RAHASIA, { expiresIn: "-1s" });
try {
  jwt.verify(tokenKadaluarsa, RAHASIA);
} catch (e) {
  console.log("error verify token kedaluwarsa:", e.name, "-", e.message);
}

// Token dengan tanda tangan salah: kunci yang berbeda dipakai saat verify.
try {
  jwt.verify(token, "rahasia-yang-salah");
} catch (e) {
  console.log("error verify tanda tangan salah:", e.name, "-", e.message);
}
text Keluaran
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfMTIzIiwibmFtYSI6IkRld2kiLCJwZXJhbiI6ImVkaXRvciJ9.5wnt1YXdi3Eg1DUT3R9RtrHptoXKgdKuh32ijR1vYU8
payload: {"sub":"usr_123","nama":"Dewi","peran":"editor"}
header: {"alg":"HS256","typ":"JWT"}
error verify token kedaluwarsa: TokenExpiredError - jwt expired
error verify tanda tangan salah: JsonWebTokenError - invalid signature

Token pada keluaran di atas terbagi atas tiga segmen yang dipisahkan titik. Segmen pertama adalah header Base64URL ({"alg":"HS256","typ":"JWT"}), segmen kedua adalah payload Base64URL ({"sub":"usr_123","nama":"Dewi","peran":"editor"}), dan segmen ketiga adalah tanda tangan HMAC-SHA256 yang dihitung dari header dan payload. jwt.verify menghitung ulang tanda tangan dengan kunci rahasia dan membandingkannya dengan tanda tangan yang tercantum pada token. Modifikasi payload sekecil apa pun mengubah tanda tangan yang dihitung ulang, sehingga selisih dengan tanda tangan asli memicu JsonWebTokenError - invalid signature.

Klaim iat dan exp#

Demo di atas memakai opsi noTimestamp: true agar token dapat direproduksi tanpa terikat waktu. Pada produksi, opsi yang lazim dipakai adalah expiresIn, yang membuat jsonwebtoken menambahkan dua klaim numerik ke payload: iat (issued at, saat token diterbitkan) dan exp (expiration, saat token kedaluwarsa). Kedua klaim ini dinyatakan sebagai detik sejak epoch Unix.

js Klaim iat dan exp memakai expiresIn
// tidak-dijalankan
const jwt = require("jsonwebtoken");
const token = jwt.sign({ sub: "usr_123" }, RAHASIA, { expiresIn: "1h" });
const decoded = jwt.verify(token, RAHASIA);
console.log(decoded.iat, decoded.exp);
// exp - iat = 3600 (satu jam)

jwt.verify memeriksa exp terhadap waktu saat ini dan melempar TokenExpiredError bila token sudah lewat masa berlakunya, sebagaimana diperlihatkan oleh keluaran error pada demo sebelumnya. Penolakan terjadi pada sisi server, sehingga klien tidak dapat memperpanjang token secara sepihak. Server penerbit bertanggung jawab mengeluarkan token baru saat token lama hampir kedaluwarsa, biasanya lewat endpoint refresh.

Perbandingan session dan JWT#

Diagram berikut menyandingkan alur permintaan pada kedua pendekatan. Bagian yang dibedakan terletak pada langkah terakhir: session melakukan pencarian ke penyimpanan bersama, sedangkan JWT cukup memverifikasi tanda tangan.

Session berbasis cookieToken JWTBrowser kirim POST /loginServer simpan session di memoriSet-Cookie: sid=abc123Cookie: sid=abc123Server cari sid di penyimpananstate tersimpan di serverBrowser kirim POST /loginServer tanda tangani tokenRespons JSON: { token }Authorization: Bearer tokenServer verifikasi tanda tanganpayload dibaca dari token
Alur session berbasis cookie vs token JWT

Pemilihan antara keduanya bergantung pada kebutuhan aplikasi. Tabel berikut merangkum perbedaan utama.

AspekSession cookieToken JWT
Tempat stateServer (memori, Redis)Klien (payload di dalam token)
VerifikasiCari pengenal di penyimpananVerifikasi tanda tangan dengan kunci rahasia
PembatalanHapus entri di penyimpananSulit, karena token valid sampai exp
PenskalaanPerlu penyimpanan bersama antar instanceTidak perlu, server hanya memegang kunci
Penyimpanan sisi klienCookie otomatis dikirimHeader Authorization dipasang manual
Eksporsi tokenTidak terlihat di JavaScript bila cookie HttpOnlyTersimpan di JavaScript bila di localStorage

Kesalahan umum#

Menyimpan token JWT di localStorage tanpa mempertimbangkan risiko XSS. localStorage dapat dibaca oleh JavaScript apa pun yang berjalan pada halaman yang sama, termasuk skrip pihak ketiga atau skrip yang disusupikan lewat XSS. Bila skrip tersebut berhasil dieksekusi, token dapat dicuri dan dipakai untuk menyamar sebagai pengguna. Alternatif yang lebih aman adalah menyimpan JWT di cookie dengan atribut HttpOnly, Secure, dan SameSite=Strict, meskipun pendekatan ini memerlukan penanganan CSRF.

Mengira JWT bersifat terenkripsi. JWT hanya ditandatangani, tidak dienkripsi: payload dapat diuraikan oleh siapa pun yang memegang token, sebagaimana ditunjukkan oleh label “payload dibaca dari token” pada kolom JWT di diagram sebelumnya. Oleh karena itu, data sensitif seperti kata sandi atau nomor kartu tidak boleh ditempatkan di payload JWT. Bila kerahasiaan payload benar-benar diperlukan, gunakan JWE (JSON Web Encryption), bukan JWT biasa.

Mengandalkan JWT tanpa exp. Token tanpa klaim exp berlaku selamanya sampai kunci rahasia diganti. Bila token bocor, token tersebut dapat dipakai tanpa batas. Tetapkan expiresIn pada setiap token akses (misalnya "15m" untuk akses token dan "7d" untuk refresh token) dan sediakan mekanisme pencabutan terpisah bila diperlukan.

Ringkasan#

  • Session berbasis cookie menyimpan state pengguna di sisi server dan mengikat pengenal ke cookie. Verifikasi dilakukan dengan mencari pengenal di penyimpanan, sehingga pembatalan mudah tetapi penskalaan memerlukan penyimpanan bersama antar instance.
  • Token JWT menandatangani payload dengan kunci rahasia, menyimpan token di sisi klien, dan diverifikasi lewat tanda tangan tanpa menyimpan state. Penskalaan lebih sederhana, tetapi pembatalan sulit dilakukan sebelum exp tercapai.
  • jwt.sign(muatan, RAHASIA, { expiresIn }) menambahkan klaim iat dan exp, sedangkan jwt.verify(token, RAHASIA) melempar TokenExpiredError bila token kedaluwarsa dan JsonWebTokenError bila tanda tangan tidak cocok.
  • JWT hanya ditandatangani, tidak dienkripsi, sehingga payload dapat dibaca siapa pun yang memegang token. Data sensitif tidak boleh ditempatkan di payload.

Referensi#