2FA dengan TOTP — Auth

TOTP (Time-based One-Time Password) adalah algoritma standar (RFC 6238) yang menghasilkan kode 6 digit yang berubah setiap 30 detik. Digunakan oleh Google Authe

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:

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: