Pernah nggak sih, kamu lagi bikin project web terus sampai di bagian login, terus bingung: “Gue harus pakai JWT atau Session nih?”
Saya pernah. Dan jujur, waktu pertama kali nemuin pertanyaan ini, jawaban yang saya dapet dari internet malah bikin tambah pusing. Ada yang bilang “JWT always better”, ada yang bilang “Session is the way to go”, ada yang bilang “pakai aja keduanya”. Mana yang bener?
Setelah bertahun-tahun bikin aplikasi web—dari yang cuma side project sampai yang dipakai ribuan user—saya akhirnya punya pemahaman yang cukup matang soal ini. Dan di artikel ini, saya mau share semuanya ke kamu. Nggak pake ribet, nggak pake bahasa textbook. Anggap aja kita lagi ngopi bareng sambil bahas autentikasi.
Kenapa Autentikasi Itu Penting Banget?
Sebelum masuk ke JWT vs Session, mari kita sepakati dulu satu hal: autentikasi adalah fondasi keamanan aplikasi web kamu.
Bayangin kamu punya rumah. Autentikasi itu kayak kunci pintu depan. Tanpa kunci, siapa aja bisa masuk, ambil barang, atau bahkan tinggal di rumah kamu. Sama halnya dengan aplikasi web—tanpa autentikasi yang benar, siapa aja bisa akses data user, melakukan transaksi, atau bahkan menghapus seluruh database.
Autentikasi sendiri artinya sederhana: memverifikasi siapa yang sedang mengakses sistem. Kamu kasih tahu server, “Hei, ini saya, si Budi. Ini buktinya.” Server kemudian ngecek bukti itu. Kalau valid, kamu diizinkan masuk.
Nah, JWT dan Session ini adalah dua cara berbeda untuk memberikan dan memverifikasi “bukti” tersebut. Dua-duanya valid. Dua-duanya dipakai di industri. Tapi cara kerjanya, kelebihan, dan kekurangannya sangat berbeda.
Session-Based Authentication: Cara Klasik yang Masih Bertahan
Session-based authentication itu sebenernya udah ada sejak lama banget—jauh sebelum JavaScript jadi populer. Ini adalah metode autentikasi yang paling banyak dipakai di era web tradisional.
Cara Kerja Session
Gimana sih sebenernya cara kerjanya? Begini alurnya:
- User login → mengirimkan username dan password ke server.
- Server memverifikasi → mengecek apakah kredensial yang diberikan valid.
- Server membuat session → menyimpan data session (seperti user ID, role, dll.) di server-side storage (memory, database, atau cache seperti Redis).
- Server mengirim session ID → melalui cookie yang disisipkan di response HTTP.
- User mengirim request berikutnya → browser secara otomatis menyertakan cookie tersebut di setiap request.
- Server memverifikasi session ID → mencocokkan session ID dengan data yang tersimpan di server. Jika cocok, request diproses.
Intinya: server menyimpan state (keadaan) dari setiap user yang sedang login. Makanya disebut “stateful authentication”.
Implementasi Sederhana Session
Supaya lebih jelas, ini contoh sederhananya pakai Express.js dan express-session:
const express = require('express');
const session = require('express-session');
const app = express();
app.use(express.json());
app.use(session({
secret: 'rahasia-banget-nih',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true, // Cookie tidak bisa diakses via JavaScript
secure: true, // Hanya kirim via HTTPS
maxAge: 1000 * 60 * 60 * 24, // Expired dalam 1 hari
sameSite: 'strict' // Proteksi CSRF
}
}));
// Endpoint login
app.post('/login', (req, res) => {
const { username, password } = req.body;
// Verifikasi kredensial (simplified)
const user = users.find(u =>
u.username === username && u.password === password
);
if (!user) {
return res.status(401).json({ error: 'Username atau password salah' });
}
// Simpan data user di session
req.session.userId = user.id;
req.session.role = user.role;
res.json({ message: 'Login berhasil!' });
});
// Middleware cek autentikasi
function isAuthenticated(req, res, next) {
if (!req.session.userId) {
return res.status(401).json({ error: 'Silakan login dulu' });
}
next();
}
// Protected route
app.get('/profile', isAuthenticated, (req, res) => {
res.json({
userId: req.session.userId,
role: req.session.role,
message: 'Ini data profil kamu'
});
});
// Endpoint logout
app.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) return res.status(500).json({ error: 'Gagal logout' });
res.clearCookie('connect.sid');
res.json({ message: 'Logout berhasil' });
});
});
Kelebihan Session
- Mudah di-revoke: Kamu bisa langsung hapus session di server kalau mau force logout user. Ini sangat penting untuk kasus seperti “user kehilangan device” atau “akun dikompromikan”.
- Data tidak terekspos ke client: Session ID memang dikirim ke client, tapi data session yang sebenarnya (user ID, role, permission) tetap aman di server.
- Sudah teruji waktu: Framework apapun pasti punya dukungan session out-of-the-box. Dokumentasi dan best practice-nya sudah sangat matang.
Kekurangan Session
- Masalah skalabilitas: Kalau kamu punya banyak server (load balancing), kamu perlu memastikan semua server bisa mengakses session storage yang sama. Ini bisa jadi bottleneck.
- Memory consumption: Setiap user yang login berarti ada data yang disimpan di server. Punya 100 ribu user aktif? Itu 100 ribu session yang harus disimpan.
- Tidak cocok untuk stateless architecture: Jika kamu ingin membangun REST API yang benar-benar stateless, session melanggar prinsip ini.
- Masalah CORS: Cookie punya aturan domain yang ketat. Kalau frontend dan backend beda domain, kamu perlu konfigurasi tambahan yang cukup tricky.
JSON Web Token (JWT): Pendekatan Stateless
JWT muncul sebagai alternatif untuk mengatasi beberapa kekurangan session. Konsepnya: ngapain server harus nyimpan state? Cukup minta client yang bawa semua informasi yang dibutuhkan.
Cara Kerja JWT
- User login → mengirimkan username dan password ke server.
- Server memverifikasi → mengecek kredensial.
- Server membuat JWT → meng-encode data user (disebut payload) ke dalam sebuah token, lalu menandatanganinya dengan secret key.
- Server mengirim JWT ke client → biasanya di response body atau header.
- Client menyimpan JWT → biasanya di localStorage, sessionStorage, atau cookie.
- Client mengirim JWT di setiap request → biasanya di header
Authorization: Bearer <token>. - Server memverifikasi JWT → mengecek signature, memastikan token tidak expired, dan memproses request.
Intinya: server tidak menyimpan apa-apa. Semua informasi yang dibutuhkan sudah ada di dalam token itu sendiri. Makanya disebut “stateless authentication”.
Struktur JWT
Sebuah JWT terdiri dari tiga bagian yang dipisahkan oleh titik (.):
- Header: Berisi algoritma yang dipakai untuk signing (misalnya HS256).
- Payload: Berisi data (claims) seperti user ID, role, waktu expired, dll.
- Signature: Hasil dari meng-encode header + payload menggunakan secret key.
Implementasi Sederhana JWT
Ini contoh implementasinya pakai Express.js dan jsonwebtoken:
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());
const SECRET_KEY = 'rahasia-banget-juga-nih';
// Endpoint login
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u =>
u.username === username && u.password === password
);
if (!user) {
return res.status(401).json({ error: 'Username atau password salah' });
}
// Buat JWT
const token = jwt.sign(
{
userId: user.id,
role: user.role,
username: user.username
},
SECRET_KEY,
{ expiresIn: '24h' } // Token berlaku 24 jam
);
res.json({
message: 'Login berhasil!',
token: token
});
});
// Middleware verifikasi JWT
function verifyToken(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Token tidak ditemukan' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, SECRET_KEY);
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: 'Token tidak valid atau expired' });
}
}
// Protected route
app.get('/profile', verifyToken, (req, res) => {
res.json({
userId: req.user.userId,
role: req.user.role,
username: req.user.username,
message: 'Ini data profil kamu'
});
});
// Logout (client-side: cukup hapus token)
app.post('/logout', (req, res) => {
// Dengan JWT murni, logout dilakukan di client
// Cukup hapus token dari penyimpanan client
res.json({ message: 'Logout berhasil. Silakan hapus token.' });
});
Kelebihan JWT
- Stateless dan skalabel: Server tidak perlu menyimpan session data. Kamu bisa menambah server baru tanpa pusing soal shared session storage.
- Cocok untuk microservices: Satu token bisa dipakai di banyak service. Service A, B, dan C bisa memverifikasi token yang sama tanpa perlu koordinasi ke satu session storage.
- Cocok untuk mobile dan SPA: Nggak masalah kalau frontend dan backend beda domain. Token tinggal disisipkan di header request.
- Payload bisa dikustomisasi: Kamu bisa memasukkan data apapun ke dalam payload (tapi hati-hati, jangan pernah masukkan data sensitif!).
Kekurangan JWT
- Sulit di-revoke: Ini masalah terbesar JWT. Sekali token dibuat, token itu tetap valid sampai expired. Kalau mau force logout, kamu butuh mekanisme tambahan (blacklist, short-lived token + refresh token, dll).
- Ukuran token lebih besar: JWT membawa payload di dalamnya, jadi ukurannya lebih besar dibanding session ID. Ini bisa jadi masalah kalau kamu sering mengirim request.
- Bisa disalahgunakan kalau nggak hati-hati: Banyak developer yang menaruh data sensitif di payload JWT, padahal payload itu tidak di-encode—hanya di-base64. Siapa aja bisa decode dan membaca isinya.
- Tidak bisa menyimpan data server-side dengan mudah: Kalau kamu butuh update data user (misalnya role berubah), kamu perlu issue token baru. Session? Tinggal update di server.
Perbandingan Langsung: JWT vs Session
Oke, sekarang kamu udah paham dua-duanya. Mari kita bandingkan secara langsung:
| Aspek | Session | JWT |
|---|---|---|
| State | Stateful (server menyimpan data) | Stateless (client membawa data) |
| Penyimpanan data | Server-side (memory, DB, Redis) | Client-side (localStorage, cookie) |
| Skalabilitas | Perlu shared storage untuk multi-server | Mudah di-scale, tidak perlu shared storage |
| Revocation | Mudah, hapus session di server | Sulit, butuh mekanisme tambahan |
| CORS | Agak tricky dengan cookie | Lebih fleksibel (bisa pakai header) |
| Ukuran | Kecil (hanya session ID) | Lebih besar (payload di dalam token) |
| Keamanan | Data aman di server | Hati-hati, payload bisa di-decode |
| Cocok untuk | Web tradisional, SPA sederhana | API, microservices, mobile, SPA |
Jadi, Kapan Harus Pakai yang Mana?
Ini bagian yang paling penting. Dan jawabannya mungkin bikin kamu kecewa: tergantung.
Tapi saya nggak akan ninggalin kamu begitu aja. Ini panduan praktis yang bisa kamu pakai:
Pakai Session Kalau:
- Kamu bikin web aplikasi monolitik dengan server-side rendering (PHP, Django, Rails, dll).
- Kamu butuh kemampuan untuk force logout user dengan mudah (misalnya aplikasi banking).
- Kamu nggak punya masalah dengan shared session storage (Redis atau database).
- Kamu ingin solusi yang sederhana dan straightforward.
Pakai JWT Kalau:
- Kamu bikin REST API yang dikonsumsi oleh banyak client (web, mobile, IoT, dll).
- Kamu punya arsitektur microservices.
- Frontend dan backend beda domain (CORS jadi pertimbangan).
- Kamu butuh stateless authentication untuk kemudahan skalabilitas.
Pakai Keduanya Kalau:
Ini sebenernya pendekatan yang sering saya pakai di production. Konsepnya: pakai JWT yang disimpan di httpOnly cookie.
Dengan cara ini, kamu dapat keamanan cookie (proteksi XSS karena httpOnly, proteksi CSRF karena sameSite) plus fleksibilitas JWT (stateless verification, bisa dipakai di banyak service).
// Contoh: JWT dikirim via httpOnly cookie
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = authenticateUser(username, password);
if (!user) {
return res.status(401).json({ error: 'Login gagal' });
}
const token = jwt.sign(
{ userId: user.id, role: user.role },
SECRET_KEY,
{ expiresIn: '15m' } // Access token: short-lived
);
const refreshToken = jwt.sign(
{ userId: user.id, type: 'refresh' },
REFRESH_SECRET,
{ expiresIn: '7d' } // Refresh token: long-lived
);
// Simpan refresh token di httpOnly cookie
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 hari
});
// Kirim access token di response body
res.json({ accessToken: token });
});
Pendekatan ini memberikan yang terbaik dari dua dunia. Access token yang short-lived mengurangi risiko kalau token bocor, dan refresh token yang disimpan di httpOnly cookie tetap aman dari serangan XSS.
Tips Keamanan yang Wajib Kamu Tahu
Terlepas kamu pakai JWT atau Session, ada beberapa prinsip keamanan yang wajib kamu ikuti:
Selalu pakai HTTPS. Ini non-negotiable. Tanpa HTTPS, token atau cookie kamu bisa di-intercept oleh attacker di jaringan yang sama (man-in-the-middle attack).
Jangan pernah simpan data sensitif di JWT payload. Payload JWT itu encoded, bukan encrypted. Siapa aja bisa decode dan membacanya. Jangan pernah taruh password, nomor kartu kredit, atau data pribadi di sana.
Kalau pakai cookie, selalu set
httpOnlydansecure.httpOnlymencegah JavaScript mengakses cookie (proteksi XSS).securememastikan cookie hanya dikirim via HTTPS.Kalau pakai JWT, pertimbangkan refresh token pattern. Jangan buat JWT yang expired-nya terlalu lama (misalnya 30 hari). Itu sangat berisiko. Pakai access token yang short-lived (15 menit) dan refresh token yang long-lived (7 hari).
Validasi input di server, jangan percaya apapun dari client. Baik JWT maupun session, keduanya bisa dimanipulasi oleh attacker yang canggih. Selalu validasi dan sanitize semua input di server-side.
Implement rate limiting di endpoint login. Ini mencegah brute force attack. Nggak peduli autentikasi kamu sebagus apapun, kalau attacker bisa coba password unlimited kali, akun user kamu tetap dalam bahaya.
Kalau kamu masih bingung soal autentikasi untuk project kamu, atau butuh bantuan implementasi yang aman dan scalable, jangan ragu buat reach out ke saya di [email protected]. Saya senang bisa bantu diskusi soal arsitektur atau review kode autentikasi kamu. Serius, nggak usah sungkan—lebih baik tanya daripada asal terapin dan ternyata ada celah keamanan!
FAQ (Pertanyaan yang Sering Ditanyakan)
1. Apakah JWT lebih aman daripada Session?
Tidak juga. Keamanan bukan soal pilih JWT atau Session, tapi bagaimana kamu mengimplementasikannya. JWT yang disimpan di localStorage bisa kena XSS. Session yang cookie-nya nggak di-set httpOnly juga bisa di-hijack. Yang paling penting adalah mengikuti best practice keamanan, apapun metode yang kamu pilih. Di kasus tertentu, Session justru lebih aman karena data sensitif tetap di server.
2. Saya pakai React/Vue/Next.js, lebih baik pakai yang mana?
Kalau kamu bikin SPA (Single Page Application) dengan backend terpisah (beda domain/port), JWT biasanya lebih praktis karena nggak pusing soal CORS. Tapi kalau kamu pakai Next.js dengan SSR (Server-Side Rendering), session-based authentication bisa jadi pilihan yang lebih simpel karena server Next.js bisa langsung akses session. Banyak framework modern seperti NextAuth.js (sekarang Auth.js) malah mendukung keduanya.
3. Apa itu Refresh Token dan kenapa saya butuh itu?
Refresh token adalah mekanisme untuk mendapatkan access token baru tanpa harus login ulang. Access token dibuat short-lived (misalnya 15 menit) supaya kalau bocor, window of vulnerability-nya kecil. Kalau access token expired, client menggunakan refresh token (yang disimpan lebih aman, biasanya di httpOnly cookie) untuk minta access token baru. Tanpa ini, kamu terpaksa harus buat access token yang long-lived, yang berarti kalau token itu bocor, attacker punya akses yang sangat lama.
4. Bisa nggak pakai JWT tapi tetap bisa revoke token?
Bisa, tapi butuh mekanisme tambahan. Cara paling umum adalah dengan token blacklist—simpan daftar token yang sudah di-revoke di Redis atau database, lalu cek setiap request. Alternatif lain adalah pakai JWT ID (jti) claim yang unik, lalu cek validitasnya di database. Tapi kalau kamu sampai harus ngecek database di setiap request, ya… itu artinya stateful lagi, kan? Jadi pertimbangkan apakah JWT memang solusi yang tepat untuk kasus kamu.
5. Bagaimana cara menghindari CSRF kalau pakai Session?
Ada beberapa cara: pertama, set cookie attribute sameSite ke strict atau lax—ini browser yang akan melindungi kamu. Kedua, implement CSRF token—server mengirim token unik yang harus disertakan di setiap request yang mengubah data (POST, PUT, DELETE). Ketiga, cek Origin dan Referer header di server. Kombinasi dari ketiga cara ini memberikan proteksi yang sangat kuat terhadap CSRF.