Episode ini membahas keamanan state di Zustand: mengapa token tidak boleh masuk ke store yang ter-persist, cara memilih storage yang aman, dan bagaimana menyusun klasifikasi state. Kalian juga belajar akses kontrol berbasis role di UI dengan otorisasi server sebagai lapisan pengaman utama.

State management bukan hanya soal performa, tetapi juga keamanan. Episode 15 membahas bagaimana memperlakukan data sensitif di dalam Zustand: mengapa token tidak boleh masuk ke store yang ter-persist, bagaimana memilih storage yang aman, dan bagaimana menyusun klasifikasi state agar aplikasi mudah dipelihara dan diperiksa. Kita juga membahas akses kontrol berbasis role di UI.
Setelah episode ini, kalian tahu cara menaruh state pada tempatnya dan menjaga data sensitif tidak bocor ke penyimpanan yang bisa dibaca siapa pun.
Middleware persist menulis state ke localStorage atau storage lain sebagai teks biasa. Siapa pun yang membuka DevTools bisa membaca nilai tersebut. Menyimpan token akses di store persist sama dengan menempel token di dinding browser.
Token sebaiknya disimpan di cookie httpOnly yang dikelola server, atau di memory store tanpa persist. Jika token harus hidup di Zustand untuk request, buat store terpisah tanpa persist:
import { create } from 'zustand'
interface TokenState {
token: string | null
setToken: (token: string) => void
clearToken: () => void
}
export const useTokenStore = create<TokenState>()((set) => ({
token: null,
setToken: (token) => set({ token }),
clearToken: () => set({ token: null }),
}))Store useTokenStore tanpa middleware persist hanya menyimpan token di memori. Saat tab ditutup, token hilang — persis yang diinginkan untuk data sesi yang sensitif.
Ketika persist tetap diperlukan, batasi data yang tersimpan dengan partialize agar hanya field aman yang ikut. Jangan pernah menyimpan secret atau PII penuh ke storage:
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
export const useUserStore = create<UserState>()(
persist(
(set) => ({
id: null,
email: '',
role: 'guest',
token: null,
setSession: (data) => set(data),
}),
{
name: 'user-prefs',
partialize: (state) => ({
email: state.email,
role: state.role,
}),
},
),
)partialize: (state) => ({ email, role }) memastikan hanya email dan role yang tersimpan, sementara token tetap berada di memori. Ini bentuk sanitasi minimal: pisahkan field sensitif dan non-sensitif dengan eksplisit.
State aplikasi layak diklasifikasikan sebelum memilih tempat menyimpannya:
Klasifikasi ini menjawab pertanyaan "state ini harus hidup di mana" tanpa berdebat ulang setiap fitur baru.
Store yang menampung segala hal menjadi sulit diuji dan diprediksi. Buat satu store per domain: useAuthStore, useCartStore, useUiStore. Saat store mulai mengurus dua hal yang tidak saling terkait, pecah menjadi slice atau store baru.
Role dan permission menentukan apa yang boleh dilihat pengguna. Simpan role di store, lalu jaga UI agar hanya merender elemen yang diizinkan:
const role = useAuthStore((s) => s.user?.role)
function AdminButton() {
if (role !== 'admin') {
return null
}
return <button onClick={() => handleDelete()}>Hapus</button>
}if (role !== 'admin') adalah lapisan UI, bukan pengaman utama. Validasi tetap wajib di server — akses kontrol UI hanya menyembunyikan, bukan mengamankan. Selalu dua lapis: frontend untuk UX, backend untuk otorisasi.
Episode 15 membahas keamanan state: tidak menyimpan token di store persist, membatasi data dengan partialize, memilih storage yang aman, mengklasifikasikan state, dan menerapkan akses berbasis role di UI dengan otorisasi server sebagai fondasi.
Inti yang harus dibawa pulang:
Di episode 16 selanjutnya kita akan membahas custom middleware dan slice pattern — menulis middleware sendiri sebagai wrapper, memakai logging dan tracking, memecah store besar menjadi slices, dan mengetik slices agar bisa saling mengakses.