Belajar 2FA Authentication - Skema Database & Data Management
Episode 12 of 23

Belajar 2FA Authentication - Skema Database & Data Management

Episode ini merancang skema database untuk 2FA: tabel users dengan kolom secret terenkripsi dan status, tabel recovery_codes dengan hash single-use, indeks dan constraint, migrasi dengan Prisma atau raw SQL, serta kebijakan backup yang melindungi data 2FA.

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

Pendahuluan

Semua status 2FA yang dibahas sejauh ini — secret terenkripsi, flag aktif, recovery codes — hidup di database. Episode 12 merancang skema tersebut dengan benar: tipe kolom yang tepat, indeks yang mendukung query verifikasi, constraint yang menjaga integritas, dan migrasi yang bisa dijalankan berulang.

Kesalahan skema yang umum — kolom secret dengan tipe terlalu kecil, recovery code disimpan sebagai teks polos, atau query yang memindai seluruh tabel — akan menyerang aplikasi saat produksi bertumbuh. Di akhir episode, kalian punya rancangan tabel siap pakai dan alur migrasi yang aman.

Skema Tabel users

Kolom untuk 2FA

Tabel users mendapat tiga kolom terkait 2FA di atas kolom autentikasi biasa. Secret disimpan terenkripsi, bukan plaintext:

Tabel users dengan kolom 2FA
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,
  last_used_step BIGINT,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

totp_secret_encrypted menyimpan string iv:tag:data dari enkripsi AES-256-GCM (episode 7). Kolom last_used_step mendukung replay protection dari episode 10, dan totp_enabled menjadi sumber kebenaran status 2FA.

Mengapa UUID dan TIMESTAMPTZ

Primary key UUID menghindari enumerasi pengguna dari id berurutan. TIMESTAMPTZ menyimpan waktu dengan zona, sehingga logika yang membandingkan waktu — seperti kedaluwarsa secret pending — tidak rusak oleh perbedaan zona server.

Skema Tabel recovery_codes

Hash dan Status Single-Use

Recovery codes disimpan satu baris per kode, dengan hash sebagai isinya:

Tabel recovery_codes
CREATE TABLE recovery_codes (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  code_hash TEXT NOT NULL,
  used_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
 
CREATE INDEX idx_recovery_codes_user ON recovery_codes(user_id);

Constraint user_id REFERENCES users(id) ON DELETE CASCADE memastikan kode ikut terhapus ketika akun dihapus. Indeks pada user_id membuat pencarian kode milik pengguna cepat, tanpa memindai seluruh tabel.

Migrasi Data Lama

Jika kalian sudah punya tabel users tanpa kolom 2FA, migrasi menambah kolom dengan default yang aman. Kolom baru bernilai null dan totp_enabled false — pengguna yang sudah ada tidak terpengaruh sampai mereka memilih mengaktifkan 2FA.

Migrasi dengan Prisma

Definisi Schema

Prisma menawarkan schema yang bisa di-migrate secara berulang. Definisi model 2FA:

Model Prisma untuk 2FA
model User {
  id                  String   @id @default(uuid())
  email               String   @unique
  passwordHash        String
  totpSecretEncrypted String?
  totpEnabled         Boolean  @default(false)
  lastUsedStep        BigInt?
  recoveryCodes       RecoveryCode[]
}
 
model RecoveryCode {
  id       String   @id @default(uuid())
  userId   String
  user     User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  codeHash String
  usedAt   DateTime?
  @@index([userId])
}

Skema totpSecretEncrypted String? adalah nilai nullable karena pengguna tanpa 2FA tidak memilikinya. Relasi recoveryCodes menampilkan kode milik pengguna lewat foreign key.

Menjalankan Migrasi

Migrasi dijalankan lewat perintah Prisma yang membuat tabel dan melacak riwayat:

Buat dan jalankan migrasi
npx prisma migrate dev --name add-2fa-columns
npx prisma migrate deploy

prisma migrate dev menulis file migrasi untuk pengembangan, sedangkan prisma migrate deploy menerapkannya di produksi. Catat versi schema di riwayat migrasi agar rollback dan kolaborasi antar developer mudah.

Backup dan Pemulihan

Backup Terenkripsi

Backup database harus ikut terenkripsi — secret 2FA di dalamnya terenkripsi oleh aplikasi, tapi lapis kedua melindungi data saat backup dipindahkan. Enkripsi backup juga menjawab persyaratan compliance seperti SOC 2 yang sering menuntut data at-rest terenkripsi.

Strategi Uji Pemulihan

Backup tanpa uji pemulihan hanyalah ilusi keamanan. Lakukan restore berkala ke environment uji dan pastikan kode TOTP tetap valid — ini sekaligus menguji bahwa MFA_ENCRYPTION_KEY yang tersimpan terpisah bisa mendekripsi data hasil backup. Kehilangan kunci enkripsi berarti secret tidak bisa dibaca, sehingga simpan cadangan kunci di tempat terpisah dari database.

Penutup

Episode 12 merancang skema database untuk 2FA: tabel users dengan secret terenkripsi dan flag status, tabel recovery_codes dengan hash single-use, indeks dan constraint, migrasi dengan Prisma, serta kebijakan backup yang teruji.

Inti yang harus dibawa pulang:

  • Secret 2FA disimpan sebagai string terenkripsi di kolom TEXT.
  • last_used_step mendukung replay protection di level database.
  • Recovery codes disimpan satu baris per kode dengan hash.
  • Pakai foreign key dengan ON DELETE CASCADE untuk recovery codes.
  • Migrasi dengan Prisma mencatat riwayat perubahan schema.
  • Enkripsi backup dan uji pemulihan secara berkala.

Di episode 13 selanjutnya kita akan membahas hardening transport: HTTPS, cookie, dan CSP — memasang TLS untuk melindungi kode TOTP di perjalanan, memastikan cookie session aman, dan mengatur Content-Security-Policy untuk halaman enrollment.

Belajar 2FA Authentication - Skema Database & Data Management | Belajar 2FA Authentication