Sebelum menambahkan lapisan 2FA, episode ini membangun fondasi autentikasi: registrasi dan login password dengan bcrypt, session cookie HttpOnly dan JWT, struktur user model dengan kolom secret TOTP, serta peta endpoint auth yang akan disisipi tantangan MFA di episode 8.

Lapisan 2FA tidak berdiri sendiri — dia menempel di atas autentikasi yang sudah ada. Episode 3 memastikan fondasi itu kuat: registrasi dan login password, hashing dengan bcrypt, session berbasis cookie HttpOnly, alternatif JWT, dan struktur user model yang akan menyimpan secret TOTP.
Kenapa episode ini penting? Karena hampir semua bug 2FA muncul dari fondasi yang salah: session yang tidak dirotasi, password plaintext, atau flag 2FA yang bisa dilewati. Dengan fondasi yang benar, episode 8 cukup menyisipkan satu langkah verifikasi di tengah alur login.
Password tidak boleh disimpan mentah. Gunakan bcrypt atau argon2 yang menambahkan salt otomatis dan lambat secara sengaja untuk memperlambat serangan offline. Install library hashing dan mulai proyek:
npm init -y
npm install express@5.2.1 bcryptjs express-session cookie-parserbcryptjs adalah implementasi bcrypt murni JavaScript yang tidak butuh kompilasi native. Pada registrasi, simpan hasil hash, bukan plaintext; pada login, bandingkan input dengan bcrypt.compare.
Saat pengguna mendaftar, server memvalidasi input, mengecek email unik, lalu menyimpan email dan password hash ke tabel users. Jangan pernah mengembalikan hash ke client. Alur login tinggal membalik prosesnya: cari user by email, verifikasi password, lalu buat session.
Untuk aplikasi berbasis browser, session yang paling aman disimpan di cookie HttpOnly dan Secure — cookie tidak bisa dibaca JavaScript sehingga tahan terhadap XSS. Setelah login berhasil, server menandatangani session dan mengirim cookie lewat header Set-Cookie.
Contoh konfigurasi express-session dengan cookie yang aman:
app.use(session({
name: 'sid',
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 30 * 60 * 1000
}
}));Perhatikan httpOnly: true dan sameSite: 'lax' — keduanya adalah garis pertahanan pertama terhadap XSS dan CSRF yang akan kita perkuat di episode 13.
Jika kalian membangun API tanpa browser (mobile app atau SPA murni), JWT adalah alternatif yang populer. Server menandatangani token dengan klaim seperti sub dan exp, client menyimpannya dan mengirimnya di header Authorization. Kelemahannya: token tidak bisa dicabut secara server-side sebelum kedaluwarsa, sehingga session server-side umumnya lebih disukai untuk fitur 2FA. Episode 15 akan membahas hubungan JWT dengan flag 2FA secara detail.
Fondasi 2FA menuntut beberapa kolom baru pada model user. Secret TOTP disimpan terenkripsi (episode 7), bukan plaintext:
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
totp_secret_encrypted TEXT,
totp_enabled BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);Tiga kolom kunci adalah totp_secret_encrypted, totp_enabled, dan nanti tabel recovery codes. Kolom totp_secret_encrypted masih kosong hingga enrollment selesai di episode 6.
Selain kolom di atas, tambahkan tabel recovery_codes yang menyimpan hash kode pemulihan single-use. Episode 9 akan membedah pembuatan dan siklus hidupnya, tapi struktur tabelnya sudah bisa dibuat sekarang agar migrasi sekali jalan.
Peta endpoint auth di proyek kalian kini terlihat seperti ini. Tantangan 2FA akan menyisip di antara POST /login dan pembuatan session penuh di episode 8:
POST /register -> buat user + hash password
POST /login -> verifikasi password, set pendingMfa jika 2FA aktif
POST /login/mfa -> verifikasi TOTP, beri session penuh
POST /logout -> hapus session
GET /account/settings -> halaman manajemen 2FAPerhatikan bahwa POST /login tidak langsung memberikan session penuh ketika 2FA aktif — ia hanya menandai pendingMfa. Inilah jembatan menuju episode 8.
Episode 3 meletakkan fondasi autentikasi: password di-hash dengan bcrypt, session disimpan di cookie HttpOnly, JWT sebagai alternatif API, user model dengan kolom secret TOTP, dan peta endpoint yang menunggu tantangan MFA.
Inti yang harus dibawa pulang:
Di episode 4 selanjutnya kita akan membahas generate secret dan otpauth URI — membuat secret acak per pengguna dengan authenticator.generateSecret dan menyusun URI provisioning yang dikenali Google Authenticator.