State management yang sehat juga berarti state yang aman. Episode ini membahas menghindari persistensi token dan secret, memilih storage yang tepat, sanitasi data dari API sebelum masuk store, serta best practice klasifikasi state, single responsibility per store, dan akses kontrol berbasis role.

Store menampung data penting aplikasi — termasuk data yang sensitif. Kesalahan umum: menyimpan token, menyimpan seluruh respons API tanpa sanitasi, atau mempersist informasi yang seharusnya tidak bertahan. Episode ini mengubah kebiasaan tersebut.
Episode 15 membahas keamanan dan best practice Pinia: menghindari persistensi secret, memilih storage yang aman, sanitasi data sebelum masuk store, serta prinsip klasifikasi state, single responsibility, dan akses kontrol berbasis role.
Aturan pertama: jangan pernah mempersist token dan secret ke localStorage. localStorage dapat dibaca oleh JavaScript mana pun yang berjalan di halaman, sehingga rentan terhadap XSS:
// JANGAN: token tersimpan di localStorage
persist: {
key: 'auth',
storage: localStorage,
}
// LEBIH AMAN: token hidup di memory (store tanpa persist)
export const useAuthStore = defineStore('auth', {
state: () => ({ token: null as string | null }),
})Simpan token hanya di state (memory) dan beri umur pendek lewat refresh token di sisi server. Jika penyimpanan persisten benar-benar dibutuhkan, pertimbangkan mekanisme yang dikendalikan server seperti httpOnly cookie.
Tidak semua data berbahaya — klasifikasikan dulu sebelum memutuskan tempat penyimpanannya:
persist.persist: {
key: 'app-preferences',
storage: localStorage,
}persist: { storage: localStorage } memang paling nyaman, tetapi kelayakannya tergantung isi state. Mulailah dengan pertanyaan: apa jadinya kalau data ini terbaca orang lain yang memakai perangkat yang sama?
Respons API tidak selalu bersih. Sebelum data masuk ke store, validasi dan pilih field yang dibutuhkan:
actions: {
async fetchProfile() {
const res = await fetch('/api/profile').then((r) => r.json())
this.profile = {
id: String(res.id ?? ''),
name: typeof res.name === 'string' ? res.name : '',
role: res.role === 'admin' ? 'admin' : 'user',
}
},
}this.profile = {...} hanya menyimpan field yang diharapkan, dengan validasi tipe dan nilai yang diperbolehkan. Pola ini mencegah data tak terduga dari API mengotori state dan merusak rendering.
Semakin sedikit data di store, semakin kecil permukaan masalahnya:
this.user = res begitu saja.Fungsi sanitasi kecil ini adalah investasi yang membayar ketika API berubah strukturnya.
Pisahkan state berdasarkan sifatnya agar perlakuan keamanannya jelas:
Klasifikasi ini juga membantu menentukan mana yang boleh dipersist dan mana yang harus tetap di memory.
Best practice arsitektur yang beririsan dengan keamanan:
actions: {
async deleteUser(id: string) {
const auth = useAuthStore()
if (auth.user?.role !== 'admin') {
throw new Error('Forbidden')
}
await fetch(`/api/users/${id}`, { method: 'DELETE' })
},
}auth.user?.role !== 'admin' menghentikan operasi sensitif di sisi logika, bukan hanya menyembunyikan tombol di UI. Ingat: pencegahan di client tidak menggantikan otorisasi di server — ini lapisan pertahanan pertama, bukan satu-satunya.
Warning
Semua data di client bisa dibaca pengguna. Jangan pernah menaruh rahasia seperti password atau secret API di store, apa pun bentuk persistence-nya.
Episode 15 membekali kalian keamanan dan best practice yang siap produksi. Kalian sekarang tahu cara menghindari persistensi secret, memilih storage aman, menyaring data dari API, serta menerapkan klasifikasi state, single responsibility, dan access control berbasis role.
Inti yang harus dibawa pulang:
Di episode 16 selanjutnya kita akan membahas TypeScript deep dive — typing store yang diinferensikan otomatis, penggunaan generics di defineStore, typing plugin dengan PiniaCustomProperties, dan type inference untuk Setup store. Pinia akan menjadi sahabat terbaik TypeScript kalian.