Pertanyaan dan Kasus Penggunaan
Sebuah aplikasi web manajemen proyek multi-tenant mendukung rendering sisi server (SSR) dan pengeditan offline. Aplikasi ini memiliki empat jenis status (state): server harus mengenali sesi login di seluruh tab; preferensi tema dan bahasa harus tetap bertahan pada kunjungan berikutnya; draf formulir multi-langkah hanya milik tab saat ini dan boleh hilang saat tab tersebut ditutup; serta hingga 10.000 rekaman tugas terstruktur dengan lampiran gambar harus mendukung kueri, pengeditan, dan sinkronisasi offline setelah terhubung kembali.
Alokasikan data ini ke cookies, localStorage, sessionStorage, dan IndexedDB. Jelaskan bagaimana desain ini menangani XSS dan CSRF, isolasi origin, render SSR pertama yang konsisten, tab konkuren, kuota dan pengeluaran data (eviction), peningkatan versi database, logout dan pembersihan lokal, serta sinkronisasi offline.
Angka 10.000 rekaman adalah batasan skenario yang mendorong pembahasan mengenai kueri terstruktur, akses asinkron, dan protokol sinkronisasi. Ini bukanlah jaminan kapasitas browser. Keterampilan intinya adalah membuat keputusan persistensi frontend berdasarkan semantik platform Web, sehingga kategorinya adalah frontend.
Apa yang Sedang Dievaluasi oleh Pewawancara
Pertama, apakah kandidat menanyakan siapa yang membaca data, berapa lama masa pakainya, bagaimana bentuknya, apa dampak jika terjadi kebocoran, dan sistem mana yang memegang otoritas sebelum memilih sebuah API? Tabel kapasitas yang sekadar dihafal tidak dapat menjelaskan SSR, autentikasi, atau konsistensi offline.
Kedua, dapatkah kandidat memisahkan persistensi dari keamanan? Persistensi tidak membuat localStorage cocok untuk sebuah pengidentifikasi sesi (session identifier). Skrip dari origin yang sama dapat membaca dan mengubahnya, sehingga payload XSS dapat mencuri atau merusak datanya. Cookie HttpOnly mencegah JavaScript membaca pengidentifikasi sesi, namun tidak menghentikan kode yang sudah berjalan di dalam halaman untuk mengirimkan permintaan yang terautentikasi.
Ketiga, apakah kandidat memahami kedua sisi perilaku cookie? Browser mengirimkan cookie yang cocok bersama permintaan, yang membantu rendering server dan autentikasi. Pengiriman otomatis yang sama ini memerlukan pertahanan CSRF. SameSite adalah salah satu pertahanan, bukan pengganti pemeriksaan origin, token CSRF, atau autentikasi ulang untuk tindakan sensitif.
Keempat, dapatkah kandidat memperlakukan IndexedDB sebagai replika lokal yang bisa gagal (fallible)? IndexedDB mendukung objek terstruktur asinkron, indeks, dan blob, tetapi tidak melakukan sinkronisasi dengan server secara otomatis. Aplikasi bertanggung jawab menangani kegagalan kuota, eviction, pembersihan, upgrade yang terblokir, dan konflik konkurensi.
Kelima, dapatkah kandidat menyediakan matriks kegagalan yang dapat dieksekusi alih-alih menganggap "data bertahan setelah refresh" sebagai satu-satunya pengujian?
Pertanyaan Klarifikasi Sebelum Menjawab
- Nilai mana saja yang dibutuhkan server untuk render pertama? Jika SSR harus menghasilkan HTML untuk status login, bahasa, atau tema yang benar, nilai-nilai tersebut memerlukan sumber yang dapat dibaca server, atau produk harus menerima adanya perubahan setelah pemasangan (mounting) di klien.
- Model autentikasi apa yang digunakan? Jawaban ini mengasumsikan sesi sisi server. Desain token API murni juga memerlukan penerbitan, rotasi, pencabutan, dan aturan permintaan lintas-situs (cross-site) yang eksplisit.
- Apakah draf tersebut benar-benar harus berskala tab? Jika pengguna mengharapkan pemulihan setelah menutup tab atau melanjutkan di perangkat lain,
sessionStoragetidak lagi memenuhi persyaratan; gunakan IndexedDB atau server. - Apakah data offline berisi informasi sensitif? Perangkat bersama, XSS, profil browser, dan pencadangan lokal mengubah apa saja yang boleh ditulis ke disk.
- Bagaimana 10.000 rekaman tersebut dikueri dan diperbarui? Kueri berdasarkan proyek, waktu pembaruan, atau status sinkronisasi membuat indeks dan transaksi menjadi lebih penting daripada akses key-value sederhana.
- Apakah server merupakan sumber kebenaran (source of truth) final? Skenario ini mengasumsikan server yang otoritatif dan salinan lokal yang dapat dibangun kembali. Data yang dibuat secara offline dan tidak tergantikan memerlukan kontrol persistensi, ekspor, dan konflik yang lebih kuat.
- Apa yang dianggap sebagai konflik? Pengeditan perangkat yang konkuren dapat menggunakan penolakan versi, penggabungan kolom (field merging), atau prioritas bisnis. API penyimpanan tidak dapat memilih kebijakan tersebut untuk produk.
- Apakah logout harus menghapus data setiap tenant? Ketika beberapa akun berbagi satu browser, pembersihan memerlukan namespace pengguna dan tenant agar pengguna berikutnya tidak dapat melihat replika pengguna sebelumnya.
Kerangka Jawaban 30 Detik
"Saya memetakan data berdasarkan pembaca, masa pakai, bentuk data, sensitivitas, dan sumber kebenaran. Autentikasi menggunakan sesi server dan cookie __Host- dengan Secure, HttpOnly, dan nilai SameSite yang sesuai, ditambah pertahanan CSRF. Preferensi kecil yang tidak sensitif menggunakan localStorage, dengan nilai awal yang dapat dibaca server jika SSR membutuhkannya. Draf sekali pakai untuk satu tab menggunakan sessionStorage. Rekaman terstruktur dan gambar menggunakan IndexedDB dengan indeks, transaksi, skema berversi, dan outbox. Server tetap menjadi pihak yang otoritatif; sinkronisasi menggunakan versi dan kunci idempoten serta menyelesaikan konflik secara eksplisit. Perlakukan penyimpanan lokal sebagai sesuatu yang dapat dihapus dan rentan terhadap batasan kuota, lalu uji XSS, CSRF, banyak tab, eviction, upgrade yang terblokir, penyambungan kembali, dan pembersihan logout."
Pembahasan Mendalam Langkah demi Langkah
Langkah 1: Bangun matriks keputusan dengan lima pertanyaan
Untuk setiap nilai, ajukan lima pertanyaan: apakah pembacanya adalah server, tab saat ini, atau setiap halaman dengan origin yang sama; apakah masa pakainya adalah satu render, satu tab, satu sesi browser, atau beberapa sesi; apakah datanya berupa string kecil, objek terstruktur, atau blob; seberapa merugikan kebocoran dan manipulasi data; serta apakah server atau perangkat lokal yang memegang otoritas?
Hal tersebut menghasilkan alokasi awal untuk skenario ini:
Login session -> Server session + random identifier in an HttpOnly cookie
Theme and language -> localStorage; add a server-readable initial value when SSR requires it
Single-tab draft -> sessionStorage
Offline data/images -> IndexedDB + an application-level synchronization protocolIni bukanlah pemeringkatan berdasarkan kapasitas. Kemampuan utama cookie di sini adalah menjangkau server bersama permintaan jaringan. Batasan utama sessionStorage adalah konteks penjelajahan tingkat atas (top-level browsing context). IndexedDB menyediakan transaksi asinkron, objek terstruktur, dan indeks.
Langkah 2: Jadikan server otoritatif untuk autentikasi
Browser hanya menyimpan pengidentifikasi sesi beringkat entropi tinggi dan berumur pendek. Izin sebenarnya, kedaluwarsa, dan pencabutan berada di server. Tetapkan Secure, HttpOnly, nilai SameSite yang sesuai, dan sebaiknya gunakan cookie __Host- tanpa Domain dan dengan path root. Logout, perubahan kata sandi, dan peristiwa risiko membatalkan sesi server; menghapus nilai di browser saja tidak cukup.
HttpOnly mengurangi akses skrip langsung ke pengidentifikasi sesi, namun XSS pada origin yang sama masih dapat melakukan tindakan terautentikasi dari halaman yang aktif. Pengodean output (output encoding), CSP, dan pertahanan XSS lainnya tetap diperlukan, dan operasi berisiko tinggi dapat mewajibkan autentikasi ulang. Karena cookie yang cocok dikirim secara otomatis, permintaan yang mengubah status (state-changing) juga harus memvalidasi origin dan menggunakan token CSRF. SameSite tidak boleh menjadi satu-satunya kontrol.
Menyimpan pengidentifikasi sesi di localStorage membuatnya dapat dibaca oleh skrip origin sama mana pun yang berhasil dijalankan. Enkripsi sisi klien bukanlah solusi otomatis: jika JavaScript di halaman dapat memperoleh kunci dekripsi atau memanggil jalur dekripsi, XSS pada origin yang sama biasanya juga dapat melakukannya.
Langkah 3: Gunakan localStorage untuk preferensi kecil yang tidak sensitif
localStorage dibatasi oleh origin (origin-scoped), menyimpan kunci dan nilai string, bertahan di berbagai sesi browser, dan mengekspos operasi sinkron. Preferensi kecil seperti tema, bahasa, atau kepadatan tabel sangat cocok di sini. Data ini tidak permanen: menghapus data situs, mengakhiri sesi penjelajahan pribadi, atau kebijakan browser dapat menghapusnya.
Server tidak dapat membaca localStorage secara langsung. Jika frame SSR pertama harus menggunakan bahasa atau tema yang benar, sinkronisasikan preferensi yang telah divalidasi ke cookie yang dapat dibaca server atau profil pengguna dan tentukan salinan mana yang menang. Jika nilainya hanya untuk klien, hasilkan default yang stabil dan beralihlah setelah proses mounting agar HTML server dan render klien pertama tidak mengalami ketidakcocokan (mismatch).
Hindari menyimpan array rekaman yang besar di localStorage dan mem-parsing seluruh nilai JSON secara berulang-ulang. Serialisasi sinkron dan akses pada thread utama meningkat seiring bertambahnya ukuran data, sementara model ini tidak memiliki transaksi dan indeks seperti IndexedDB.
Langkah 4: Simpan hanya status tab sekali pakai di sessionStorage
sessionStorage dipartisi berdasarkan origin dan konteks penjelajahan tingkat atas. Data ini bertahan saat reload di tab yang sama dan dihapus ketika tab atau jendela ditutup. Hal ini membuatnya tepat untuk nomor langkah tab saat ini dan draf sekali pakai, tetapi tidak untuk keranjang belanja lintas-tab, pemulihan jangka panjang, atau status lintas-perangkat.
Batasan pembuka (opener boundary) mudah terlewatkan. Halaman yang baru dibuka awalnya mungkin menerima salinan sessionStorage dari pembukanya; kedua salinan tersebut kemudian berubah secara independen. Jika draf tidak boleh disalin sama sekali, hapus relasi opener atau sertakan ID instans per-tab di dalam draf dan validasi selama pemulihan.
Skrip dari origin yang sama juga dapat membaca dan menulis sessionStorage. "Dihapus saat ditutup" bukanlah alasan untuk menyimpan kredensial berumur panjang. Sebelum menyimpan draf secara persisten, kecualikan kolom seperti kata sandi, data pembayaran, atau informasi medis yang tidak boleh ditulis ke disk.
Langkah 5: Bangun replika offline yang dapat dibuat ulang di IndexedDB
IndexedDB menawarkan permintaan asinkron, transaksi, object store, kunci, indeks, dan penyimpanan blob. Ini cocok untuk rekaman terstruktur dan gambar pada skenario ini. Partisi data berdasarkan tenantId + userId, buat object store untuk tugas, lampiran, dan outbox, serta tambahkan hanya indeks yang diperlukan oleh kueri riil seperti proyek, waktu pembaruan, dan status sinkronisasi.
Gunakan versi database yang eksplisit dan migrasi bertahap (incremental). Pembukaan versi baru dapat diblokir oleh koneksi dari tab lama. Tab lama tersebut harus mendengarkan versionchange, menutup koneksi lamanya, dan meminta pengguna untuk me-refresh. Halaman baru harus menangani blocked alih-alih mengalami freeze selamanya saat startup.
IndexedDB adalah database lokal, bukan layanan sinkronisasi. Penulisan yang berhasil berarti transaksi lokal telah di-commit. Aplikasi tetap harus mencatat versi lokal dan server, ID operasi, serta status sinkronisasi, dan memastikan percobaan ulang bersifat idempoten.
Langkah 6: Rancang sinkronisasi offline dan konflik lintas-tab secara eksplisit
Commit setiap perubahan bisnis offline dan entri outbox-nya dalam satu transaksi IndexedDB. Setelah terhubung kembali, sebuah worker mengunggah berdasarkan ID operasi. Server melakukan deduplikasi dengan kunci idempoten dan membandingkan versi rekaman. Hanya setelah konfirmasi (acknowledgement) diterima, transaksi lain boleh memperbarui versi server dan menghapus item outbox. Hal ini mencegah pengubahan rekaman kehilangan operasi yang tertunda.
Kebijakan konflik berasal dari aturan bisnis. Preferensi berisiko rendah dapat menggunakan pemenang penulisan terakhir (last writer wins); status tugas dapat menolak ketidakcocokan versi dan meminta pengguna untuk menggabungkannya; perubahan keuangan atau izin dapat melarang commit offline. Peristiwa storage atau BroadcastChannel dapat memberitahu tab lain untuk memuat ulang data, namun notifikasi bukanlah sebuah lock dan tidak dapat menggantikan transaksi IndexedDB atau pemeriksaan versi server. Peristiwa storage tidak terpicu pada dokumen yang melakukan penulisan.
Langkah 7: Perlakukan kuota, penggusuran (eviction), dan pembersihan sebagai kegagalan normal
Kuota browser bervariasi berdasarkan browser, perangkat, dan mode. IndexedDB umumnya menggunakan penyimpanan best-effort, yang dapat dihapus oleh pengguna dan dapat digusur (evicted) oleh browser di bawah tekanan ruang penyimpanan. Penulisan juga dapat gagal karena kekurangan kuota. Gunakan estimasi penyimpanan untuk memantau penggunaan, minta penyimpanan persisten secara hati-hati untuk data yang tidak tergantikan, dan selalu tangani kegagalan transaksi serta kuota.
Kebijakan harus mencakup batas lampiran, pembersihan least-recently-used (LRU), kompresi atau penghapusan versi lama yang telah dikonfirmasi server, dan status pemulihan saat "ruang penyimpanan lokal penuh". Saat logout, cabut sesi server terlebih dahulu, lalu tutup koneksi database dan hapus data IndexedDB, preferensi, serta draf milik pengguna dan tenant tersebut. Beri tahu tab lain juga. Penghapusan lokal tidak dapat menggantikan pencabutan sesi di server.
Langkah 8: Verifikasi batasan dengan matriks kegagalan
Uji render SSR pertama dan hidrasi; refresh, duplikasi, pembukaan tab baru, dan penutupan tab; logout dari tab lain; cakupan yang dapat dibaca skrip di bawah ancaman XSS; permintaan lintas-situs yang mengubah status; penjelajahan pribadi; pembersihan data situs; kehabisan kuota; tab lama yang memblokir upgrade database; dua tab yang mengedit secara konkuren; percobaan ulang offline, respons duplikat dan di luar urutan, serta konflik; dan pergantian tenant tanpa mengekspos data lama.
Lolos pengujian berarti lebih dari sekadar "data tetap ada." Autentikasi dapat dicabut, nilai sensitif tidak terekspos ke JavaScript, draf mematuhi batasan tab, penulisan offline tidak menghilangkan entri outbox, sinkronisasi duplikat tidak menduplikasi efek bisnis, proses upgrade dapat pulih, kegagalan kuota memiliki fallback, dan server dapat membangun kembali replika lokal.
Contoh Jawaban Berkualitas Tinggi
"Saya memulai dari pembaca, masa pakai, model data, batas kepercayaan, dan sumber kebenaran alih-alih kapasitas. Server dan setiap tab membutuhkan sesi login, sehingga sesi yang sebenarnya berada di server dan browser memegang cookie __Host- dengan atribut Secure, HttpOnly, dan nilai SameSite yang sesuai. Hal itu mengurangi akses token oleh JavaScript, namun XSS masih dapat bertindak atas nama pengguna, dan pengiriman cookie otomatis tetap memerlukan token CSRF, pemeriksaan origin, serta autentikasi ulang untuk tindakan sensitif.
Tema dan bahasa adalah preferensi kecil yang tidak sensitif, sehingga menggunakan localStorage. Jika SSR membutuhkannya pada frame pertama, saya menyinkronkan preferensi tervalidasi yang dapat dibaca server dan menentukan salinan yang otoritatif. Draf formulir sekali pakai untuk tab saat ini menggunakan sessionStorage: reload akan memulihkannya dan penutupan tab akan menghapusnya. Saya juga memperhitungkan halaman baru yang awalnya menyalin nilai dari pembukanya.
10.000 tugas dan gambar menggunakan IndexedDB. Database dipartisi berdasarkan pengguna dan tenant serta menggunakan skema berversi, indeks, dan transaksi. Setiap pengeditan bisnis dan entri outbox di-commit secara bersamaan. Saat terhubung kembali, klien mengunggah dengan ID operasi yang idempoten, dan server membandingkan versi rekaman sebelum memberikan konfirmasi atau mengembalikan konflik. Notifikasi lintas-tab hanya memicu pemuatan ulang; transaksi lokal dan versi server yang menyediakan kebenaran data.
Saya memperlakukan semua penyimpanan lokal sebagai data yang dapat dihapus, rentan gagal karena kuota, dan dapat diakses oleh skrip dari origin yang sama. Saya menangani error kuota, membersihkan lampiran yang dapat dibangun kembali, menutup koneksi lama selama proses upgrade, dan mencabut sesi server sebelum membersihkan namespace lokal pengguna saat logout. Matriks pengujian saya mencakup SSR dan hidrasi, penyalinan tab, XSS, CSRF, kuota, eviction, upgrade yang terblokir, pengeditan konkuren, percobaan ulang offline, dan peralihan tenant."
Kesalahan Umum
- Memilih secara mekanis dari tabel kapasitas → mengabaikan pembaca server, batasan tab, dan otoritas → gunakan matriks lima dimensi terlebih dahulu.
- Menyimpan pengidentifikasi sesi di
localStorage→ XSS pada origin yang sama dapat membaca dan mengeksfiltrasi data tersebut → gunakan cookie yang dilindungi dan didukung oleh sesi server serta tetap lakukan pencegahan XSS. - Mengasumsikan
HttpOnlymengeliminasi XSS → skrip berbahaya masih dapat melakukan tindakan terautentikasi → pisahkan pertahanan pencurian token dari pertahanan eksekusi tindakan. - Hanya menggunakan
SameSiteuntuk CSRF → kebijakan browser, jenis permintaan, dan alur bisnis masih memiliki batasan → gabungkan token, pemeriksaan origin, dan autentikasi ulang. - Membuat server bergantung pada
localStorage→ SSR tidak dapat membaca penyimpanan browser → sediakan nilai awal yang dapat dibaca server atau terima perubahan setelah mounting. - Mengasumsikan tab baru selalu memiliki
sessionStorageyang kosong → relasi opener dapat memberikan salinan awal → hapus opener atau tambahkan ID instans tab. - Menyimpan array besar di
localStorage→ memerlukan parsing sinkron, penulisan ulang seluruh nilai, dan tidak memiliki transaksi terindeks → gunakan IndexedDB. - Mengasumsikan IndexedDB melakukan sinkronisasi secara otomatis → commit lokal dan konfirmasi server adalah peristiwa yang berbeda → terapkan outbox, idempotensi, versi, dan kebijakan konflik.
- Memperlakukan notifikasi lintas-tab sebagai distributed lock → pesan dapat tertunda dan tidak dapat memutuskan konflik server → muat ulang setelah notifikasi dan gunakan transaksi serta versi untuk menjaga kebenaran data.
- Mengasumsikan data lokal bersifat permanen → pembersihan, penggusuran (eviction), mode privat, dan kuota dapat menghapusnya → buat data dapat dibangun kembali dan tangani kegagalan penulisan.
- Hanya menghapus cookie saat logout → IndexedDB dan preferensi dapat bocor ke akun berikutnya → cabut sesi di sisi server, lalu bersihkan setiap replika lokal milik pengguna dan tenant.
Pertanyaan Lanjutan dan Tanggapan
Pertanyaan Lanjutan 1: Bisakah saya mengenkripsi token dan menyimpannya di localStorage?
Jika JavaScript di halaman dapat memperoleh kunci atau memanggil jalur dekripsi, skrip berbahaya dari origin yang sama yang berhasil berjalan biasanya juga dapat melakukannya. Hal itu tidak menyelesaikan masalah pencurian sesi akibat XSS dalam skenario ini. Batasan yang lebih kuat adalah sesi server dengan cookie HttpOnly ditambah pencegahan XSS, perlindungan CSRF, rotasi, dan pencabutan sesi. Enkripsi sisi klien hanya mengatasi ancaman jika kuncinya berada di luar attack surface yang sama.
Pertanyaan Lanjutan 2: Bagaimana jika draf harus tetap ada setelah tab ditutup?
Persyaratan masa pakai telah berubah, sehingga sessionStorage tidak lagi cocok. Draf tidak sensitif yang hanya memerlukan pemulihan lokal dapat menggunakan IndexedDB. Draf yang harus dapat diakses di berbagai perangkat atau tidak boleh hilang harus disinkronkan ke server. Kedua opsi tersebut memerlukan periode retensi, isolasi pengguna dan tenant, serta aturan yang mengecualikan kolom sensitif dari persistensi.
Pertanyaan Lanjutan 3: Bagaimana jika dua tab mengedit tugas offline yang sama?
Simpan versi server dan revisi lokal pada setiap rekaman dan gunakan transaksi IndexedDB untuk setiap penulisan. BroadcastChannel atau notifikasi perubahan data memberi tahu tab lain untuk memuat ulang, namun server tetap melakukan pembaruan kondisional terhadap versinya. Setelah terjadi konflik, aturan bisnis menentukan apakah akan melakukan penggabungan kolom, memunculkan prompt ke pengguna, atau menolaknya. Tab terakhir yang menerima pesan tidak dapat menjadi sumber kebenaran.
Pertanyaan Lanjutan 4: Bagaimana jika tab lama memblokir upgrade IndexedDB?
Koneksi lama mendengarkan versionchange, menutup koneksi, dan meminta pengguna untuk refresh. Halaman baru menangani blocked dengan status yang dapat dipulihkan alih-alih menunggu tanpa batas. Migrasi bersifat inkremental, idempoten, dan mentoleransi data historis parsial. Sebelum rilis, biarkan tab versi lama tetap terhubung dan buka versi baru untuk memverifikasi penutupan koneksi, pengiriman pesan, dan migrasi.
Pertanyaan Lanjutan 5: Bagaimana aplikasi harus melakukan penurunan performa secara halus (graceful degradation) saat penyimpanan browser penuh?
Tangani error kuota dan transaksi, hentikan caching lampiran baru terlebih dahulu, dan hapus lampiran serta versi lama yang telah dikonfirmasi server dan dapat dibangun kembali. Outbox yang belum disinkronkan memiliki prioritas lebih tinggi daripada cache yang dapat diunduh ulang. Jika ruang penyimpanan masih belum cukup, beri tahu pengguna untuk terhubung kembali dan melakukan sinkronisasi atau mengosongkan ruang; jangan pernah melaporkan operasi penyimpanan yang gagal secara diam-diam sebagai berhasil.
Pertanyaan Lanjutan 6: Bagaimana urutan logout yang benar?
Pertama, minta server untuk mencabut sesi saat ini sehingga cookie yang tersalin dan tab yang masih terbuka juga kehilangan akses. Kemudian beri tahu tab lain, hentikan proses sinkronisasi, tutup koneksi IndexedDB, hapus database, draf, dan preferensi untuk pengguna serta tenant tersebut, dan terakhir arahkan ke UI keluar (signed-out). Jika permintaan jaringan gagal, jangan menampilkan logout lokal saja sebagai konfirmasi berhasil; batasi aktivitas selanjutnya dan tunjukkan bahwa pencabutan sesi sedang tertunda.