Aplikasi catatan Siti menyimpan preferensi tampilan dan draf tulisan di browser: halaman terasa pribadi, cepat, dan tetap mengingat pekerjaan pengguna meski koneksi terputus. Alatnya sudah kita kuasai di section Browser API — localStorage, sessionStorage, dan cookie. Pertanyaan yang tersisa adalah pertanyaan keamanan: data apa yang boleh dititipkan di sana, dan data mana yang harus dijauhi?
Aturan dasarnya satu: storage browser adalah bagian dari halaman. Setiap skrip yang berjalan di halaman — milik kita, milik pustaka, atau skrip yang masuk lewat celah XSS dari chapter Cross-Site Scripting — bisa membaca seluruh isinya. Maka keputusan penyimpanan tidak boleh diambil dari pertanyaan “praktis di mana?”, melainkan “apa akibatnya bila data ini terbaca skrip lain?”. Kuota storage yang besar (ratusan kilobait hingga sekitar lima megabita) menuntun soal kapasitas, bukan keamanan — ukuran tidak pernah menjadi tanda bahwa tempat itu lebih terlindungi.
Keputusan Penyimpanan dalam Kode #
Berkas berikut memformalkan keputusan itu: satu function yang menjawab pertanyaan “data jenis ini disimpan di mana?” berdasarkan jenis datanya, bukan berdasarkan tempat yang paling dekat. Bagian kedua berkasnya menyimulasikan mengapa pertanyaan itu penting:
// pilih-penyimpanan.js — keputusan penyimpanan berdasarkan jenis data
function pilihPenyimpanan(jenis) {
const keputusan = {
"preferensi-tampilan": "localStorage — bukan rahasia dan hanya milik pengguna ini",
"draft-catatan": "localStorage — nyaman ditulis ulang, isinya bukan rahasia",
"hasil-cari-terakhir": "sessionStorage — cukup hidup selama tab terbuka",
"token-sesi": "cookie HttpOnly yang ditetapkan server — jangan disimpan di localStorage",
"data-kartu-pembayaran": "jangan disimpan di browser, di mana pun",
};
return keputusan[jenis] ?? "periksa dulu sensitivitas datanya";
}
console.log(pilihPenyimpanan("preferensi-tampilan"));
console.log(pilihPenyimpanan("token-sesi"));
console.log(pilihPenyimpanan("daftar-tontonan"));
// simulasi: skrip apa pun yang berjalan di halaman bisa membaca localStorage
const gudang = {
tema: "gelap",
"token-sesi": "sesi-fiktif-siti-9f21",
};
function skripPihakKetiga() {
return Object.entries(gudang).filter(([kunci]) => kunci.includes("token"));
}
console.log(skripPihakKetiga());Output yang Dihasilkan:
localStorage — bukan rahasia dan hanya milik pengguna ini
cookie HttpOnly yang ditetapkan server — jangan disimpan di localStorage
periksa dulu sensitivitas datanya
[ [ 'token-sesi', 'sesi-fiktif-siti-9f21' ] ]Mari kita bedah:
- Baris 2–11 (tabel keputusan): setiap jenis data dipetakan ke tempat beserta alasannya. Pemetaan eksplisit seperti ini membuat keputusan bisa dibaca ulang tim — dan bila ada data baru,
??pada baris 10 memaksa jawaban “periksa dulu” alih-alih menitipkannya ke mana saja. - Baris 13–15 (tiga keputusan): preferensi tampilan dan draf catatan aman di
localStoragekarena bukan rahasia; token sesi ditolak dari storage dan dialihkan ke cookieHttpOnlyyang ditetapkan server. - Baris 18–21 (gudang simulasi): object
gudangberperan sebagai isi storage halaman — satu nilai ringan (tema) dan satu nilai sensitif (token-sesi, nilai fiktif). - Baris 23–27 (skrip pihak ketiga): function ini mewakili skrip apa pun yang berjalan di halaman. Ia tidak membobol apa pun — cukup
Object.entriesdanfilter— dan keluaran baris keempat memperlihatkan entri token terbaca utuh. Di halaman sungguhan,localStorage.getItem("token-sesi")memberi hasil yang sama kepada skrip mana pun.
Cookie HttpOnly vs localStorage #
Kalau storage terbaca semua skrip, penanda sesi login perlu tempat lain. Browser menyediakannya lewat atribut cookie yang sudah diperkenalkan di chapter Cookies Overview — HttpOnly:
| Aspek | cookie HttpOnly | localStorage |
|---|---|---|
| Siapa menulis | server, lewat header Set-Cookie | kode JavaScript |
| Dapat dibaca skrip | tidak — tidak muncul di document.cookie | ya, semua isinya |
| Dikirim ke server | otomatis pada setiap permintaan ke situs asal | tidak — harus dikirim manual |
| Terlihat oleh skrip XSS | isinya tidak | seluruh isinya |
Pembagian perannya jelas: penanda sesi ditetapkan server sebagai cookie HttpOnly, sehingga pembacaan oleh skrip — termasuk skrip yang lolos dari celah — tidak menghasilkan apa-apa. localStorage tetap menjadi tempat yang tepat untuk data kenyamanan yang memang bukan rahasia.
Dua atribut pendamping dari chapter Cookies Overview melengkapi gambaran ini: Secure memastikan cookie hanya berpindah lewat koneksi terenkripsi, dan SameSite=Lax (nilai bawaan browser modern) membatasi pengiriman cookie pada permintaan lintas situs. Keduanya bekerja di tingkat browser dan server — tanpa satu baris JavaScript pun.
Perlu jujur diakui batasnya: HttpOnly menyembunyikan isi cookie dari pembacaan skrip, tetapi tidak menghentikan skrip jahat untuk memakai sesi — permintaan yang dikirim skrip tetap membawa cookie. Karena itu pertahanan tidak berhenti di sini: server tetap memvalidasi setiap aksi, dan kebijakan CSP dari chapter sebelumnya mengurangi peluang skrip itu berjalan sama sekali.
Bila Token Memang Harus di Klien #
Sebagian aplikasi nyata memang membutuhkan token di sisi klien — misalnya untuk memanggil API yang tidak melalui server kita. Bila kebutuhan itu tidak bisa dihindari, kurangi dampaknya alih-alih menganggapnya selesai:
- Simpan di memori (variabel) alih-alih storage, bila hidupnya cukup selama halaman terbuka; hilang bersama muat ulang adalah batas, bukan cacat.
- Batasi masa hidup: token dengan kedaluwarsa pendek menutup jendela penyalahgunaan.
- Batasi cakupan: token yang hanya boleh membaca profil lebih ringan akibatnya daripada token yang punya hak penuh.
- Hapus saat logout dan saat sesi berakhir — storage yang tertinggal adalah penemuan yang mudah bagi skrip berikutnya.
Aturan induknya tetap dari chapter Secrets dan Client-Side Code: secret key tidak pernah ke klien. Yang dibahas di sini adalah token akses terbatas yang sengaja diberikan aplikasi — bedanya jauh dengan kredensial yang membuka semuanya.
Tabel Keputusan #
Seluruh chapter — dan section ini — diringkas dalam satu tabel:
| Data | Tempat | Catatan |
|---|---|---|
| Preferensi tampilan, bahasa, filter | localStorage | bukan rahasia, milik pengguna ini |
| Draf catatan | localStorage | isinya diketahui pengguna sendiri |
| Cache hasil pencarian untuk tab ini | sessionStorage | terhapus bersama tab |
| Penanda sesi login | cookie HttpOnly dari server | tidak disentuh JavaScript |
| Secret key / token API | server (pola proxy) | lihat chapter Secrets dan Client-Side Code |
| Data kartu pembayaran | tidak disimpan di browser | urusan penyedia pembayaran |
Section Security tuntas: kita mengenali vektor XSS, menampilkan data dengan aman, tidak mengeksekusi string, menyaring tautan, menjaga dependensi, menitipkan rahasia pada tempatnya, menambah lapisan CSP, dan memilih penyimpanan yang benar. Kebiasaan seperti ini paling terjaga bila diperiksa otomatis — dan itulah topik section berikutnya, Testing.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
pilih-penyimpanan.js
Unduh berkas// pilih-penyimpanan.js — keputusan penyimpanan berdasarkan jenis data
function pilihPenyimpanan(jenis) {
const keputusan = {
"preferensi-tampilan": "localStorage — bukan rahasia dan hanya milik pengguna ini",
"draft-catatan": "localStorage — nyaman ditulis ulang, isinya bukan rahasia",
"hasil-cari-terakhir": "sessionStorage — cukup hidup selama tab terbuka",
"token-sesi": "cookie HttpOnly yang ditetapkan server — jangan disimpan di localStorage",
"data-kartu-pembayaran": "jangan disimpan di browser, di mana pun",
};
return keputusan[jenis] ?? "periksa dulu sensitivitas datanya";
}
console.log(pilihPenyimpanan("preferensi-tampilan"));
console.log(pilihPenyimpanan("token-sesi"));
console.log(pilihPenyimpanan("daftar-tontonan"));
// simulasi: skrip apa pun yang berjalan di halaman bisa membaca localStorage
const gudang = {
tema: "gelap",
"token-sesi": "sesi-fiktif-siti-9f21",
};
function skripPihakKetiga() {
return Object.entries(gudang).filter(([kunci]) => kunci.includes("token"));
}
console.log(skripPihakKetiga());