Sistem otentikasi adalah fondasi keamanan untuk setiap aplikasi web modern. Di antara berbagai metode yang ada, JSON Web Token (JWT) telah menjadi standar emas, terutama untuk API stateless yang sering digunakan oleh aplikasi mobile atau single-page application (SPA).
Jika Anda seorang developer yang sedang membangun API dengan Express.js dan ingin mengimplementasikan otentikasi yang kuat, efisien, dan skalabel, Anda berada di tempat yang tepat. Artikel ini akan memandu Anda langkah demi langkah untuk membangun sistem otentikasi JWT dari nol di Express.js, mulai dari registrasi pengguna, proses login, hingga melindungi rute API Anda.
Mengapa JWT Penting untuk Otentikasi API Modern?
JWT adalah metode aman untuk merepresentasikan klaim antar dua pihak. Dalam konteks otentikasi, server akan membuat token (JWT) yang berisi informasi identitas pengguna setelah login berhasil. Token ini kemudian dikirimkan ke klien, yang akan menyertakannya dalam setiap permintaan ke API yang dilindungi.
Kelebihan utama JWT adalah sifatnya yang stateless. Server tidak perlu menyimpan informasi sesi pengguna; cukup memverifikasi tanda tangan digital JWT yang diterima. Ini sangat menguntungkan untuk aplikasi terdistribusi, microservices, dan API yang diakses dari berbagai klien. Dibandingkan sesi tradisional, JWT juga mengurangi beban server dan mempermudah skalabilitas.
Namun, dalam praktiknya, implementasi JWT harus dilakukan dengan hati-hati. Kunci rahasia (secret key) harus dijaga dengan sangat aman, dan pengelolaan expiration serta potensi revocation token perlu diperhatikan agar tidak menjadi celah keamanan.
Persiapan Lingkungan Proyek Express.js
Mari kita mulai dengan menyiapkan proyek Express.js dasar kita. Pastikan Anda sudah menginstal Node.js dan npm (atau yarn) di sistem Anda.
1. Inisialisasi Proyek
Buat folder baru untuk proyek Anda, lalu masuk ke dalam folder tersebut dan inisialisasi proyek Node.js:
mkdir express-jwt-auth
cd express-jwt-auth
npm init -y
2. Instalasi Dependensi
Kita akan membutuhkan beberapa paket penting:
- express: Framework web untuk Node.js.
- jsonwebtoken: Untuk membuat dan memverifikasi JWT.
- bcryptjs: Untuk mengenkripsi kata sandi pengguna secara aman.
- dotenv: Untuk mengelola variabel lingkungan (seperti kunci rahasia) dari file
.env.
Instal semua dependensi ini dengan perintah:
npm install express jsonwebtoken bcryptjs dotenv
3. Struktur Folder Proyek
Untuk menjaga kode tetap terorganisir, mari buat struktur folder sederhana. Anda bisa menyesuaikannya nanti sesuai kebutuhan proyek Anda.
express-jwt-auth/
├── .env
├── app.js
├── routes/
│ └── auth.js
├── middleware/
│ └── auth.js
└── package.json
File app.js akan menjadi titik masuk aplikasi kita. Folder routes akan berisi definisi rute API, dan middleware akan berisi fungsi middleware otentikasi kita.
Konfigurasi Variabel Lingkungan (.env)
Penting untuk menyimpan kunci rahasia JWT dan informasi sensitif lainnya di luar kode sumber utama menggunakan variabel lingkungan. Ini mencegah kebocoran informasi saat kode di-deploy. Buat file bernama .env di root proyek Anda dan tambahkan baris berikut:
File: .env
PORT=3000
JWT_SECRET=iniadalahkuncirahasiayangsuperaman
JWT_EXPIRES_IN=1h
Penting: Ganti iniadalahkuncirahasiayangsuperaman dengan string acak yang kuat dan panjang. Anda bisa menggunakan generator string acak untuk ini. JWT_EXPIRES_IN menentukan berapa lama token akan berlaku (misalnya, 1 jam).
Membuat Aplikasi Express Dasar (app.js)
Sekarang, mari siapkan file app.js untuk menginisialisasi Express dan memuat variabel lingkungan.
File: app.js
require('dotenv').config(); // Memuat variabel lingkungan dari .env
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware untuk parsing JSON body
app.use(express.json());
// Contoh rute dasar
app.get('/', (req, res) => {
res.send('API Authentication JWT Berjalan!');
});
// Menjalankan server
app.listen(PORT, () => {
console.log(Server berjalan di port ${PORT});
});
Jalankan node app.js di terminal Anda, dan Anda akan melihat pesan “Server berjalan di port 3000”.
Membuat Model User Sederhana (Simulasi Database)
Dalam proyek nyata, Anda akan menggunakan database (seperti MongoDB dengan Mongoose, atau PostgreSQL dengan Sequelize) untuk menyimpan data pengguna. Untuk tujuan tutorial ini, kita akan mensimulasikan penyimpanan pengguna dalam bentuk array di memori. Ini cukup untuk menunjukkan alur otentikasi JWT.
Buat file users.js di root proyek Anda.
File: users.js
const users = []; // Array untuk menyimpan user (simulasi database)
module.exports = users;
Kita akan mengimpor array ini di rute otentikasi kita.
Membuat Rute Otentikasi (Registrasi dan Login)
Sekarang kita akan membuat rute untuk registrasi (signup) dan login pengguna. Buat file auth.js di folder routes/.
File: routes/auth.js
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const users = require('../users'); // Import simulasi database user
const router = express.Router();
// Rute untuk Registrasi User
router.post('/register', async (req, res) => {
try {
const { username, password } = req.body;
// Periksa apakah user sudah ada
if (users.find(u => u.username === username)) {
return res.status(400).json({ message: 'Username sudah digunakan.' });
}
// Hash password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
const newUser = { id: users.length + 1, username, password: hashedPassword };
users.push(newUser);
res.status(201).json({ message: 'User berhasil didaftarkan.', user: { id: newUser.id, username: newUser.username } });
} catch (error) {
res.status(500).json({ message: 'Server error saat registrasi.' });
}
});
// Rute untuk Login User
router.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
// Cari user
const user = users.find(u => u.username === username);
if (!user) {
return res.status(400).json({ message: 'Kredensial tidak valid.' });
}
// Bandingkan password
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(400).json({ message: 'Kredensial tidak valid.' });
}
// Buat JWT Token
const payload = {
user: {
id: user.id,
username: user.username
}
};
jwt.sign(
payload,
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN },
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
} catch (error) {
res.status(500).json({ message: 'Server error saat login.' });
}
});
module.exports = router;
Jangan lupa untuk mengintegrasikan rute ini ke dalam app.js. Tambahkan baris ini setelah app.use(express.json());:
Di app.js:
// Rute API otentikasi
app.use('/api/auth', require('./routes/auth'));
Membuat Middleware untuk Proteksi Rute
Middleware ini akan bertugas untuk memeriksa validitas JWT yang dikirimkan klien pada setiap permintaan ke rute yang dilindungi. Jika token valid, permintaan akan dilanjutkan. Jika tidak, permintaan akan ditolak.
Buat file auth.js di folder middleware/.
File: middleware/auth.js
const jwt = require('jsonwebtoken');
module.exports = function(req, res, next) {
// Ambil token dari header
const token = req.header('x-auth-token');
// Periksa jika tidak ada token
if (!token) {
return res.status(401).json({ message: 'Tidak ada token, otorisasi ditolak.' });
}
// Verifikasi token
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded.user; // Tambahkan user dari token ke objek request
next(); // Lanjutkan ke middleware/handler berikutnya
} catch (error) {
res.status(401).json({ message: 'Token tidak valid.' });
}
};
Menggunakan Middleware untuk Melindungi Rute
Sekarang kita bisa menggunakan middleware auth untuk melindungi rute apa pun. Mari buat rute sederhana yang hanya bisa diakses oleh pengguna yang sudah terotentikasi.
Tambahkan rute ini di app.js (setelah deklarasi rute otentikasi):
const auth = require('./middleware/auth');
// Rute yang dilindungi
app.get('/api/protected', auth, (req, res) => {
res.json({ message: Selamat datang, ${req.user.username}! Ini adalah data rahasia., user: req.user });
});
Pengujian API dengan Postman/Insomnia
Mari kita uji API yang sudah kita bangun menggunakan tool seperti Postman atau Insomnia.
1. Registrasi Pengguna Baru
- Buat permintaan POST ke
http://localhost:3000/api/auth/register. - Di bagian Body, pilih
rawdanJSON. - Kirim data berikut:
- Anda akan menerima respons sukses dengan pesan “User berhasil didaftarkan.”
{
"username": "tester123",
"password": "passwordaman"
}
2. Login Pengguna
- Buat permintaan POST ke
http://localhost:3000/api/auth/login. - Di bagian Body, pilih
rawdanJSON. - Kirim data yang sama:
- Jika berhasil, Anda akan menerima respons yang berisi
tokenJWT. Salin token ini!
{
"username": "tester123",
"password": "passwordaman"
}
3. Mengakses Rute yang Dilindungi
- Buat permintaan GET ke
http://localhost:3000/api/protected. - Secara default, permintaan ini akan gagal dengan status 401 karena tidak ada token.
- Untuk menambahkan token, masuk ke bagian Headers.
- Tambahkan header baru:
- Key:
x-auth-token - Value: Paste token JWT yang Anda dapatkan dari langkah login sebelumnya.
- Kirim permintaan lagi. Sekarang, Anda akan mendapatkan respons sukses dengan pesan “Selamat datang, tester123! Ini adalah data rahasia.”
Selamat! Anda telah berhasil mengimplementasikan otentikasi JWT dasar di Express.js.
Masalah yang Sering Terjadi
Dalam pengalaman saya mengimplementasikan JWT, ada beberapa masalah umum yang sering dihadapi developer:
1. Token Invalid atau Expired
- Gejala: Pesan error “Token tidak valid.” atau “jwt expired”.
- Penyebab: Token yang Anda kirimkan sudah kadaluarsa (melebihi waktu
expiresInyang ditentukan) atau ada kesalahan saat membuat/menyalin token sehingga tanda tangannya tidak cocok. - Solusi: Pastikan Anda selalu menggunakan token terbaru yang valid dari proses login. Periksa juga nilai
JWT_EXPIRES_INdi.env. Untuk skenario produksi, pertimbangkan penggunaan refresh token untuk memperpanjang sesi pengguna tanpa harus login ulang.
2. Missing Secret Key atau Mismatch
- Gejala: Server error saat mencoba membuat atau memverifikasi token, seringkali dengan pesan seperti “secretOrPrivateKey must have a value”.
- Penyebab: Variabel lingkungan
JWT_SECRETbelum diatur dengan benar atau tidak dimuat olehdotenv. Bisa juga terjadi ketika ada perbedaan kunci rahasia antara saat token dibuat dan saat diverifikasi (misalnya, dua server menggunakan kunci yang berbeda). - Solusi: Pastikan
.envada di root proyek,require('dotenv').config()dipanggil paling awal diapp.js, danJWT_SECRETmemiliki nilai. Pastikan semua instansi aplikasi Anda menggunakan kunci rahasia yang sama.
3. Password Hashing Tidak Benar
- Gejala: Meskipun username dan password yang dimasukkan benar, proses login selalu gagal dengan pesan “Kredensial tidak valid.”
- Penyebab: Ada masalah saat hashing password saat registrasi atau saat membandingkan password yang dimasukkan dengan hash yang tersimpan. Ini bisa karena salah menggunakan
bcrypt.hash()ataubcrypt.compare(), atau salt round yang tidak konsisten. - Solusi: Periksa kembali implementasi
bcryptdi rute registrasi dan login. Pastikan Anda menggunakanawaituntuk operasi asinkron. Pastikan Anda juga tidak secara tidak sengaja mengubah password yang belum di-hash saat menyimpannya.
Pengalaman dan Pertimbangan Praktis
Membangun sistem otentikasi dengan JWT bukan hanya tentang menulis kode, tetapi juga memahami implikasinya di dunia nyata:
1. Kapan Menggunakan JWT?
JWT sangat cocok untuk:
- API Stateless: Saat Anda tidak ingin menyimpan sesi di server, ideal untuk microservices atau API yang diakses banyak klien.
- Aplikasi Mobile/SPA: Klien dapat menyimpan token dengan aman (misalnya di local storage atau secure storage) dan menyertakannya di setiap permintaan.
- Otentikasi Pihak Ketiga: Untuk sistem single sign-on (SSO) atau otentikasi dengan penyedia identitas eksternal.
2. Keterbatasan dan Tantangan Keamanan
- Statelessness dan Logout: Karena JWT tidak disimpan di server, proses logout tradisional (menghancurkan sesi) tidak berlaku. Anda harus mengimplementasikan mekanisme blacklist token atau mengandalkan waktu kedaluwarsa token. Di project skala kecil hal ini mungkin tidak terlalu terasa, tetapi di project besar, logout yang instan adalah fitur krusial.
- Kunci Rahasia (Secret Key): Ini adalah titik paling rentan. Jika kunci rahasia bocor, penyerang dapat membuat token palsu yang valid. Pastikan kunci rahasia sangat kuat dan tidak disimpan di repository kode.
- Penyimpanan Token di Klien: Menyimpan JWT di local storage di browser memiliki risiko XSS. Menyimpan di httpOnly cookie lebih aman dari XSS, tetapi rentan terhadap CSRF (jika tidak ada mitigasi). Untuk aplikasi mobile, gunakan secure storage.
- HTTPS Wajib: Selalu gunakan HTTPS untuk semua komunikasi API untuk mencegah token dicuri melalui serangan man-in-the-middle. Tanpa HTTPS, token akan dikirimkan dalam bentuk teks biasa.
3. Pengelolaan Token Lebih Lanjut (Refresh Token)
Untuk meningkatkan pengalaman pengguna dan keamanan, banyak implementasi JWT menggunakan dua jenis token:
- Access Token: Token berumur pendek (misalnya 5-15 menit) yang digunakan untuk otentikasi permintaan API.
- Refresh Token: Token berumur panjang (misalnya beberapa hari/minggu) yang digunakan untuk mendapatkan access token baru setelah yang lama kedaluarsa. Refresh token biasanya disimpan di httpOnly cookie dan divalidasi di server.
Menerapkan refresh token menambah kompleksitas namun secara signifikan meningkatkan keamanan dan UX, karena memungkinkan logout instan dan mengurangi jendela waktu token yang dapat dicuri.
Dalam pengujian saya, penggunaan bcryptjs untuk hashing password cukup cepat untuk sebagian besar aplikasi, bahkan untuk ratusan ribu pengguna. Namun, untuk aplikasi dengan beban otentikasi sangat tinggi, pastikan untuk memonitor performa CPU saat hashing.
FAQ
Apa itu JWT?
JWT (JSON Web Token) adalah standar terbuka (RFC 7519) untuk membuat token berbasis JSON yang secara aman mengirimkan informasi antar dua pihak. Informasi ini dapat diverifikasi karena ditandatangani secara digital menggunakan kunci rahasia atau pasangan kunci publik/privat.
Bagaimana cara kerja JWT?
Ketika pengguna login, server membuat JWT yang berisi data pengguna dan menandatanganinya dengan kunci rahasia. Token ini dikirim ke klien. Klien menyimpan token dan menyertakannya dalam setiap permintaan ke server (biasanya di header “Authorization”). Server kemudian memverifikasi tanda tangan token untuk memastikan keaslian dan validitasnya sebelum memproses permintaan.
Apakah JWT aman untuk menyimpan data sensitif?
Tidak. Meskipun JWT ditandatangani untuk menjamin integritasnya (tidak diubah), JWT itu sendiri tidak terenkripsi secara default. Siapa pun yang memiliki token dapat membaca isinya (bagian payload) jika didekode. Oleh karena itu, JWT hanya boleh menyimpan data non-sensitif atau data yang memang dimaksudkan untuk publik.
Apa bedanya JWT dengan Session?
Sesi (Session) adalah mekanisme stateful di mana server menyimpan informasi sesi pengguna di memorinya (atau database). Klien hanya menerima ID sesi (biasanya dalam cookie). JWT bersifat stateless; server tidak menyimpan informasi sesi. Semua informasi yang diperlukan untuk otentikasi ada di dalam token itu sendiri. JWT lebih cocok untuk skalabilitas dan aplikasi terdistribusi, sementara sesi lebih mudah diimplementasikan untuk logout instan.
Bagaimana cara menangani logout dengan JWT?
Karena JWT bersifat stateless, Anda tidak bisa “menghancurkan” token dari sisi server. Untuk penanganan logout instan, ada beberapa pendekatan: (1) Mengandalkan waktu kedaluwarsa token yang pendek. (2) Menerapkan blacklist token di server untuk token yang baru saja logout. (3) Menggunakan refresh token yang dapat dicabut.
Kesimpulan
Implementasi otentikasi JWT di Express.js adalah keterampilan esensial bagi setiap developer backend modern. Dengan mengikuti panduan ini, Anda telah berhasil membangun fondasi otentikasi yang kuat dan siap untuk aplikasi skala produksi. Ingatlah bahwa keamanan adalah proses berkelanjutan. Selalu perbarui dependensi Anda, lindungi kunci rahasia dengan serius, dan terus pelajari praktik terbaik keamanan web.
Sistem ini memberikan fleksibilitas tinggi untuk berbagai jenis klien dan sangat cocok untuk arsitektur API yang berorientasi layanan mikro. Dengan pemahaman yang mendalam tentang cara kerja JWT dan potensi tantangannya, Anda sekarang lebih siap untuk membangun aplikasi yang aman dan efisien.
TAGS: express.js, jwt, authentication, node.js, keamanan api, web development, backend, javascript, developer tools


