TOTP (Time-based One-Time Password) adalah algoritma standar (RFC 6238) yang menghasilkan kode 6 digit yang berubah setiap 30 detik. Digunakan oleh Google Authenticator, Authy, dan aplikasi MFA lainnya.
Cara kerja TOTP:
TOTP dibangun di atas HOTP (HMAC-based OTP). Komponen utama:
- Secret key — disimpan di server dan di app authenticator (di-share saat setup via QR code)
- Time step — jendela waktu 30 detik (T = floor(unixtime / 30))
- HMAC-SHA1 — fungsi hash yang menggabungkan secret + time step
const crypto = require("crypto");
const base32 = require("hi-base32");
function generateTOTP(secret, timestamp = Date.now()) {
// 1. Hitung time counter (30 detik window)
const timeStep = Math.floor(timestamp / 1000 / 30);
// 2. Encode counter sebagai 8-byte big-endian buffer
const counter = Buffer.alloc(8);
counter.writeUInt32BE(Math.floor(timeStep / 0x100000000), 0);
counter.writeUInt32BE(timeStep & 0xffffffff, 4);
// 3. Decode secret dari Base32
const key = Buffer.from(base32.decode.asBytes(secret));
// 4. Hitung HMAC-SHA1
const hmac = crypto.createHmac("sha1", key).update(counter).digest();
// 5. Dynamic truncation
const offset = hmac[19] & 0xf;
const otp = ((hmac[offset] & 0x7f) << 24
| (hmac[offset + 1] & 0xff) << 16
| (hmac[offset + 2] & 0xff) << 8
| (hmac[offset + 3] & 0xff)) % 1000000;
// 6. Pad dengan leading zeros jika perlu
return otp.toString().padStart(6, "0");
}
function verifyTOTP(secret, code, timestamp = Date.now()) {
// Cek window saat ini dan satu window sebelumnya (toleransi clock skew)
for (let window = -1; window <= 1; window++) {
const t = timestamp + window * 30 * 1000;
if (generateTOTP(secret, t) === code) return true;
}
return false;
}
Setup TOTP untuk user:
const speakeasy = require("speakeasy");
const qrcode = require("qrcode");
// 1. Generate secret saat user aktifkan 2FA
const secret = speakeasy.generateSecret({ name: "BelajarWeb.dev" });
// secret.base32 → simpan terenkripsi di database
// secret.otpauth_url → buat QR code untuk di-scan
const qrUrl = await qrcode.toDataURL(secret.otpauth_url);
// Tampilkan QR code ke user, minta verifikasi kode pertama
// 2. Verifikasi setelah scan
const valid = speakeasy.totp.verify({
secret: secret.base32,
encoding: "base32",
token: req.body.code,
window: 1
});
// 3. Jika valid, tandai 2FA sebagai aktif di database
await db.users.update({ mfaSecret: secret.base32, mfaEnabled: true }, { where: { id: userId } });
Alur login dengan TOTP:
POST /login { email, password }
→ Verifikasi password ✓
→ Cek mfaEnabled: true
→ Response: { requiresMfa: true, tempToken: "..." }
POST /login/mfa { tempToken, totpCode }
→ Validasi tempToken (berlaku 5 menit)
→ Verifikasi TOTP code
→ Beri access + refresh token
Best practices TOTP:
- Rate limit percobaan kode (max 5x per 10 menit)
- Simpan "used codes" untuk mencegah reuse dalam window yang sama
- Sediakan recovery codes (8-10 kode one-time backup)
- Jangan kirim secret via email — tampilkan QR di UI secara langsung