Episode ini mengamankan aplikasi TanStack: penyimpanan token yang aman, auth headers pada fetcher, refresh token flow, router guards dengan beforeLoad, serta role-based data fetching dan access control.

Query dan mutasi kalian sekarang membawa data berharga. Waktunya mengamankannya. Autentikasi menentukan siapa user, dan secure data fetching menentukan data apa yang boleh diambil oleh siapa. TanStack tidak menyediakan auth bawaan — tapi memberikan semua titik yang kalian butuhkan untuk mengintegrasikannya.
Episode 12 membahas penyimpanan token yang aman, auth headers pada fetcher, refresh token flow, router guards, serta role-based data fetching. Pola-pola ini membangun lapisan keamanan di sekitar query dan route.
Prinsip utamanya sederhana: token dijaga ketat, request selalu membawa kredensial yang benar, dan akses di-cek di dua tempat — router untuk navigasi, query function untuk data.
Cara paling aman menyimpan token akses adalah HTTPOnly cookie yang ditetapkan server. Cookie tidak bisa dibaca JavaScript, sehingga kebal terhadap serangan XSS yang mencuri token dari localStorage:
Set-Cookie: access_token=abc123; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900HttpOnly; Secure; SameSite=Strict membuat cookie hanya dikirim lewat HTTPS, tidak dikirim lintas situs, dan tidak bisa diakses kode browser. Jika memakai localStorage karena keterbatasan backend, minimalkan durasi token dan selalu validasi input agar XSS tidak sampai mencurinya.
Daripada menulis header Authorization di setiap query, bungkus menjadi satu fetcher. Semua query function memakai fetcher ini:
async function authFetch(url, options = {}) {
const token = await getToken()
const res = await fetch(url, {
...options,
headers: {
Authorization: `Bearer ${token}`,
...options.headers,
},
})
if (res.status === 401) {
await refreshToken()
return authFetch(url, options)
}
return res
}
const { data } = useQuery({
queryKey: ["profil"],
queryFn: () => authFetch("/api/profil").then((res) => res.json()),
})authFetch menyisipkan Authorization dan menangani 401 secara transparan. Saat token basi, ia memanggil refreshToken, lalu mengulang request. Query function tetap bersih — kerumitan auth tersembunyi di satu tempat.
Refresh token memiliki masa hidup panjang dan digunakan hanya untuk mendapatkan access token baru. Alur yang benar: access token kadaluarsa, request 401, fetch token baru, ulangi request asli:
let refreshPromise = null
async function refreshToken() {
if (!refreshPromise) {
refreshPromise = fetch("/api/auth/refresh", {
method: "POST",
credentials: "include",
}).then((res) => res.json())
}
return refreshPromise.finally(() => {
refreshPromise = null
})
}refreshPromise menyimpan satu proses refresh yang dibagikan. Jika beberapa request 401 bersamaan, semuanya menunggu promise yang sama alih-alih memicu refresh berulang — mencegah banjir request dan race condition.
TanStack Router melindungi route lewat beforeLoad. Jika syarat tidak terpenuhi, throw redirect agar user dipindahkan:
import { redirect } from "@tanstack/react-router"
const dashboardRoute = createRoute({
getParentRoute: () => rootRoute,
path: "dashboard",
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: "/login" })
}
},
component: DashboardComponent,
})context.auth berasal dari router context yang diisi saat login. throw redirect({ to: "/login" }) menghentikan navigasi dan memindahkan user. Guard bisa dipasang di route induk sehingga melindungi seluruh anaknya sekaligus.
Auth tidak cukup sampai ke level data. Kombinasikan guard route dengan query function yang menyesuaikan dengan peran user:
const adminRoute = createRoute({
getParentRoute: () => rootRoute,
path: "admin",
beforeLoad: ({ context }) => {
if (context.auth.role !== "admin") {
throw redirect({ to: "/dashboard" })
}
},
component: AdminComponent,
})Guard memastikan hanya user berperan admin yang bisa membuka halaman admin. Di sisi data, query function memakai peran sebagai bagian dari queryKey dan otorisasi tetap diverifikasi server. Router guard hanya mengatur pengalaman; server yang menjadi penjaga terakhir.
Warning
Guard di front-end tidak pernah cukup untuk keamanan. Selalu lakukan otorisasi di backend, karena user bisa memanipulasi kode browser. Guard TanStack Router hanya kenyamanan UX.
Episode 12 mengamankan aplikasi kalian: token disimpan di HTTPOnly cookie, authFetch membawa kredensial dan menangani 401, refresh token di-deduplicate dengan shared promise, router di-guard lewat beforeLoad, dan akses dibatasi per peran.
Inti yang harus dibawa pulang:
Di episode 13 selanjutnya kita akan membahas API integration dan caching strategy — integrasi REST dan GraphQL, strategi cache dengan staleTime dan gcTime, invalidasi untuk data real-time, serta offline support dengan persist cache. Query kalian akan belajar berkomunikasi dengan berbagai backend!