Belajar 2FA Authentication - Rate Limiting & Replay Protection
Episode 10 of 23

Belajar 2FA Authentication - Rate Limiting & Replay Protection

Episode ini memperkuat endpoint verifikasi: rate limiting dengan express-rate-limit untuk mempersempit brute-force 6 digit, dan replay protection dengan melacak time-step terakhir agar kode TOTP tidak bisa dipakai ulang dalam jendela 30 detik yang sama.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Kode TOTP 6 digit memiliki ruang pencarian hanya satu juta kemungkinan. Tanpa pengaman tambahan, penyerang bisa menebak kode dengan mencoba ribuan kali per detik. Episode 10 menutup dua celah yang saling melengkapi: rate limiting untuk memperlambat percobaan, dan replay protection agar kode yang sudah sah tidak dipakai ulang.

Keduanya berbeda tapi saling menopang. Rate limiting menyerang bagian serangan, replay protection menyerang kelemahan protokol. Kalian akan memasang keduanya pada endpoint verifikasi MFA, dan memahami cara kerjanya di balik layar.

Rate Limiting pada Endpoint MFA

express-rate-limit

express-rate-limit@7 dipasang sebagai middleware pada route yang paling sensitif: verifikasi kode. Batasan umumnya 5 percobaan per menit per IP atau per akun:

JSRate limit endpoint verifikasi MFA
const { rateLimit } = require('express-rate-limit');
 
const mfaLimiter = rateLimit({
  windowMs: 60 * 1000,
  limit: 5,
  standardHeaders: 'draft-7',
  legacyHeaders: false
});
 
app.post('/login/mfa', mfaLimiter, mfaHandler);
app.post('/account/enable-2fa/confirm', mfaLimiter, confirmHandler);

Middleware mfaLimiter menghitung permintaan per IP dalam jendela 60 detik. Setelah 5 percobaan, permintaan berikutnya ditolak dengan status 429 sampai jendela bergeser. Opsi standardHeaders mengirim header RateLimit yang bisa dibaca client.

Memilih Cakupan: IP atau Akun

Rate limit berbasis IP mencegah penyerang tunggal, tapi bisa menjebak pengguna di belakang NAT bersama. Rate limit berbasis akun lebih adil tapi bisa dilewati penyerang dengan banyak akun. Solusi terbaik adalah menggabungkan keduanya: limit ketat per akun dan limit longgar per IP sebagai jaring pengaman.

Batas pada Alur Enrollmen

Jangan lupa melindungi endpoint konfirmasi enrollment dengan limit yang sama. Secret 6 digit yang diketik saat aktivasi sama rapuhnya dengan kode login, dan penyerang dengan akses ke secret pending bisa menebak kode untuk membajak enrollment.

Sebagai aturan praktis, semua route yang memproses 6 digit TOTP harus memakai limit yang sama ketatnya dengan /login/mfa — konsistensi ini lebih mudah diuji daripada daftar pengecualian yang panjang.

Replay Protection

Masalah: Kode Bisa Dipakai Dua Kali

Window toleransi yang diizinkan otplib (misalnya window 1) membuat kode tetap valid selama 90 detik. Dalam rentang itu, kode yang sama bisa dipakai ulang jika server tidak melacak pemakaian — misalnya penyerang yang menyadap satu kode memakainya sebelum pemilik asli. RFC 6238 menyebut kontrol ini dengan istilah mitigasi replay.

Melacak Time-Step Terakhir

Solusinya sederhana: simpan time-step terakhir yang berhasil, dan tolak kode yang berasal dari step yang sudah tercatat:

JSReplay protection berbasis time-step
const currentStep = Math.floor(Date.now() / 1000 / 30);
 
if (user.lastUsedStep === currentStep) {
  return res.status(400).json({ error: 'Kode sudah dipakai' });
}
 
const valid = authenticator.check(req.body.token, decryptSecret(user.totp_secret_encrypted));
if (!valid) {
  return res.status(400).json({ error: 'Kode tidak valid' });
}
 
await users.update({
  where: { id: user.id },
  data: { lastUsedStep: currentStep }
});

Kolom lastUsedStep menyimpan nilai step yang terakhir berhasil. Jika percobaan berikutnya datang dengan step yang sama, server langsung menolak sebelum menghitung kode — murah dan efektif. Update dilakukan hanya setelah kode benar.

Alur Kode yang Valid

Visualisasi tiga step dalam satu jendela:

Window toleransi dan replay
step-1   step        step+1
 15 dtk   30 dtk      30 dtk     <- rentang kode valid
   x  (sudah dipakai)  x  (dipakai sekarang)
        terakhir dipakai = step   <- step yang sama ditolak

Kode dari step yang sama ditolak meski masih dalam jendela toleransi — inilah yang membedakan toleransi waktu dari izin pemakaian ganda.

Kesalahan yang Sering Terjadi

Rate Limit Terlalu Longgar

Limit di bawah 10 percobaan per menit untuk 6 digit sering dianggap cukup, tapi sebenarnya masih memberi penyerang puluhan ribu percobaan per jam dengan banyak IP. Tetapkan limit ketat di layer aplikasi, lalu perketat lagi di layer edge seperti CDN atau firewall.

Update lastUsedStep Sebelum Verifikasi

Mengupdate lastUsedStep sebelum memeriksa kode akan memblokir pengguna sah yang mengetik kode salah pada step yang sama. Update harus selalu berjalan setelah verifikasi berhasil — urutan di kode contoh di atas bukan kebetulan.

Penutup

Episode 10 menutup dua celah pada endpoint verifikasi: rate limiting memperlambat brute-force 6 digit dengan express-rate-limit, dan replay protection menolak kode yang sudah dipakai melalui pencatatan lastUsedStep.

Inti yang harus dibawa pulang:

  • Batasi percobaan verifikasi MFA sekitar 5 kali per menit.
  • Gabungkan rate limit per akun dan per IP.
  • Lindungi juga endpoint konfirmasi enrollment.
  • Simpan time-step terakhir untuk menolak pemakaian ulang kode.
  • Periksa lastUsedStep sebelum menghitung kode.
  • Update lastUsedStep hanya setelah kode valid.

Di episode 11 selanjutnya kita akan membahas disable, reset, dan rotasi secret — mematikan 2FA dengan re-autentikasi, mengganti secret saat perangkat dicurigai bocor, dan siklus hidup status pengguna dari disabled ke pending lalu enabled.

Belajar 2FA Authentication - Rate Limiting & Replay Protection | Belajar 2FA Authentication