Autentikasi: Hash Password & Token
Menyimpan password dengan aman (salt + hash), membuat token bertanda tangan, dan melindungi route dengan middleware.
Autentikasi vs otorisasi
- Autentikasi: "kamu siapa?" — membuktikan identitas (login).
- Otorisasi: "kamu boleh melakukan apa?" — hak akses (admin, pengguna biasa).
Aturan emas: jangan simpan password asli
Jika database bocor (dan itu sering terjadi), password asli membahayakan penggunamu di semua situs. Simpan hash: hasil fungsi satu arah yang tak bisa dibalik menjadi password. Saat login, hitung hash dari password yang diketik lalu bandingkan.
- Hash biasa seperti MD5/SHA-256 saja tidak cukup (terlalu cepat; mudah ditebak dengan tabel pelangi/brute force).
- Pakai salt: string acak unik per pengguna yang digabung sebelum hashing, sehingga password yang sama menghasilkan hash berbeda.
- Pakai fungsi hash yang sengaja lambat:
bcrypt,scrypt,argon2, atauPBKDF2dengan iterasi besar.
const crypto = require('crypto');
function hashPassword(password, salt = crypto.randomBytes(8).toString('hex')) {
const hash = crypto.pbkdf2Sync(password, salt, 1000, 32, 'sha256').toString('hex');
return { salt, hash };
}
function cekPassword(password, salt, hashTersimpan) {
const hash = crypto.pbkdf2Sync(password, salt, 1000, 32, 'sha256');
return crypto.timingSafeEqual(hash, Buffer.from(hashTersimpan, 'hex'));
}
Di kode nyata pakai iterasi jauh lebih besar (mis. ≥ 100.000 untuk PBKDF2) atau pustaka bcrypt/argon2. Angka 1000 di sini hanya agar latihan cepat. timingSafeEqual mencegah serangan yang mengukur waktu perbandingan.
Token: bukti bahwa kamu sudah login
Setelah login berhasil, server memberi klien token. Klien menyertakannya di tiap permintaan berikutnya sehingga server tak perlu meminta password lagi. Bentuk umum: JWT (JSON Web Token): header.payload.tanda-tangan. Payload berisi data (ID pengguna, waktu kedaluwarsa); tanda tangan dibuat dengan kunci rahasia server sehingga tak bisa dipalsukan tanpa kunci itu. (Payload hanya di-encode, bukan dienkripsi: jangan taruh rahasia di dalamnya.)
const SECRET = process.env.TOKEN_SECRET || 'rahasia-kelasit';
function buatToken(payload, detik = 3600) {
const isi = Buffer.from(JSON.stringify({ ...payload, exp: Date.now() + detik * 1000 })).toString('base64url');
const ttd = crypto.createHmac('sha256', SECRET).update(isi).digest('base64url');
return `${isi}.${ttd}`;
}
function bacaToken(token) {
const [isi, ttd] = String(token).split('.');
if (!isi || !ttd) return null;
const benar = crypto.createHmac('sha256', SECRET).update(isi).digest('base64url');
if (ttd !== benar) return null; // dipalsukan / diubah
const payload = JSON.parse(Buffer.from(isi, 'base64url').toString());
return payload.exp > Date.now() ? payload : null; // kedaluwarsa?
}
Di proyek nyata gunakan pustaka teruji seperti jsonwebtoken atau jose daripada membuat sendiri — contoh di atas menunjukkan prinsipnya.
Middleware pelindung
function wajibLogin(req, res, next) {
const [skema, token] = (req.get('authorization') || '').split(' ');
const payload = skema === 'Bearer' ? bacaToken(token) : null;
if (!payload) return res.status(401).json({ error: 'Token tidak valid' });
req.user = payload; // titipkan untuk handler berikutnya
next();
}
app.get('/profil', wajibLogin, (req, res) => res.json(req.user));
Klien mengirim header Authorization: Bearer <token>.
Alur lengkap
POST /register— validasi, hash password, simpan{ salt, hash }(bukan password).POST /login— cari pengguna, cocokkan hash; jika cocok balas{ token }, jika tidak 401 dengan pesan umum ("email atau password salah" — jangan bocorkan mana yang salah).- Route terlindungi memakai middleware
wajibLogin.
Keamanan tambahan
- Selalu HTTPS di produksi.
- Kunci rahasia dari variabel lingkungan, bukan dari kode.
- Batasi percobaan login (rate limit).
- Token berumur pendek + refresh token untuk aplikasi serius.
- Jangan mengirim
salt/hashke klien: hapus dari respons.