Belajar Pinia - Security & Best Practice
Episode 15 of 23

Belajar Pinia - Security & Best Practice

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.

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

Pendahuluan

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.

Data Sensitif dan Storage yang Aman

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:

JSPola yang tidak aman
// 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.

Memilih Storage Sesuai Sifat Data

Tidak semua data berbahaya — klasifikasikan dulu sebelum memutuskan tempat penyimpanannya:

  • Preferensi UI (tema, bahasa): aman di localStorage lewat persist.
  • Sesi aktif: simpan di memory; biarkan refresh token sisi server yang memperpanjang sesi.
  • Data sesi-sensitif (profil, saldo): jangan dipersist tanpa enkripsi dan review keamanan.
JSContoh memilih storage per store
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?

Sanitasi Data dari API

Respons API tidak selalu bersih. Sebelum data masuk ke store, validasi dan pilih field yang dibutuhkan:

JSSanitasi respons API
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.

Batasi Field yang Disimpan

Semakin sedikit data di store, semakin kecil permukaan masalahnya:

  • Hanya simpan field yang benar-benar dirender atau dipakai logika.
  • Jangan menyalin seluruh object API tanpa proses — misalnya this.user = res begitu saja.
  • Buat fungsi pemetaan yang terpusat agar validasi konsisten di semua store.

Fungsi sanitasi kecil ini adalah investasi yang membayar ketika API berubah strukturnya.

Klasifikasi State

Pisahkan state berdasarkan sifatnya agar perlakuan keamanannya jelas:

  • UI state: modal terbuka, tema, sidebar — boleh dipersist, tidak sensitif.
  • Global state: sesi, preferensi — perlakukan dengan hati-hati.
  • Server state: data dari API — kelola lewat Colada atau Vue Query (episode 14).

Klasifikasi ini juga membantu menentukan mana yang boleh dipersist dan mana yang harus tetap di memory.

Single Responsibility dan Access Control

Best practice arsitektur yang beririsan dengan keamanan:

  • Satu store mengurus satu domain — jangan campur token di store cart.
  • Jangan tampilkan data sensitif lewat getter yang bisa diakses sembarang komponen.
  • Lakukan akses kontrol berbasis role di action, bukan di template saja:
JSGuard berbasis role di action
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.

Penutup

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:

  • Token dan secret tidak boleh dipersist ke localStorage.
  • Simpan token di memory; gunakan refresh token sisi server.
  • Sanitasi respons API sebelum masuk store.
  • Klasifikasikan state: UI, global, dan server.
  • Satu store untuk satu domain; jangan campur data sensitif.
  • Terapkan access control di action, bukan hanya di template.

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.

Belajar Pinia - Security & Best Practice | Belajar Pinia