30 Nov 2025·4 menit membaca

Guard routing Vue 3 untuk akses berbasis peran: pola praktis

Guard routing Vue 3 untuk akses berbasis peran dijelaskan dengan pola praktis: aturan route meta, redirect aman, fallback 401/403 ramah, dan cara menghindari kebocoran data.

Guard routing Vue 3 untuk akses berbasis peran: pola praktis

const routes = [ { path: "/admin", component: () => import("@/pages/AdminLayout.vue"), meta: { requiresAuth: true, roles: ["admin"] }, children: [ { path: "users", component: () => import("@/pages/AdminUsers.vue"), // inherits requiresAuth + roles from parent }, { path: "audit", component: () => import("@/pages/AdminAudit.vue"), meta: { permissions: ["audit:read"] }, }, ], }, { path: "/tickets", component: () => import("@/pages/Tickets.vue"), meta: { requiresAuth: true, permissions: ["tickets:read"], readOnly: true }, }, ] \n\nUntuk nested route, putuskan bagaimana aturan digabungkan. Di kebanyakan aplikasi, children harus mewarisi requirement parent. Di guard Anda, periksa setiap matched route record (jangan hanya `to.meta`) agar aturan parent tidak terlewat.\n\nSatu detail yang menghemat waktu nanti: bedakan antara “bisa melihat” dan “bisa mengedit”. Sebuah route mungkin terlihat untuk support dan admin, tetapi aksi edit harus dinonaktifkan untuk support. Flag `readOnly: true` di `meta` bisa menggerakkan perilaku UI (menonaktifkan aksi, menyembunyikan tombol destruktif) tanpa berpura-pura itu adalah keamanan.\n\n## Siapkan state auth agar guard berperilaku andal\n\nSebagian besar bug guard berasal dari satu masalah: guard berjalan sebelum aplikasi mengetahui siapa pengguna.\n\nPerlakukan auth seperti mesin status kecil dan jadikan itu satu sumber kebenaran. Anda ingin tiga status jelas:\n\n- **unknown**: aplikasi baru dimulai, sesi belum diperiksa\n- **logged out**: pengecekan sesi selesai, tidak ada pengguna valid\n- **logged in**: pengguna dimuat, role/permission tersedia\n\nAturan: jangan pernah membaca role saat auth berstatus **unknown**. Itu yang menyebabkan tampilan singkat dari layar yang dilindungi atau pengalihan mengejutkan ke login.\n\n### Tentukan bagaimana refresh sesi bekerja\n\nPilih satu strategi refresh dan pertahankan agar dapat diprediksi (mis. baca token, panggil endpoint “who am I”, set user).\n\nPola stabil terlihat seperti ini:\n\n- Saat aplikasi dimuat, set auth ke **unknown** dan mulai satu permintaan refresh tunggal\n- Resolusi guard hanya setelah refresh selesai (atau timeout)\n- Cache user di memory, bukan di route meta\n- Jika gagal, set auth ke **logged out**\n- Ekspos sebuah promise `ready` (atau serupa) yang bisa ditunggu oleh guard\n\nSetelah ini terpasang, logika guard tetap sederhana: tunggu auth siap, lalu putuskan akses.\n\n## Langkah demi langkah: implementasi otorisasi di level route\n\nPendekatan bersih adalah menjaga sebagian besar aturan di satu guard global, dan gunakan per-route guard hanya bila sebuah route benar-benar butuh logika khusus.\n\n### 1) Tambahkan global `beforeEach` guard\n\njs // router/index.js router.beforeEach(async (to) => { const auth = useAuthStore()

// Step 2: wait for auth initialization when needed if (!auth.ready) await auth.init()

// Step 3: check authentication, then roles/permissions if (to.meta.requiresAuth && !auth.isAuthenticated) { return { name: 'login', query: { redirect: to.fullPath } } }

const roles = to.meta.roles if (roles && roles.length > 0 && !roles.includes(auth.userRole)) { return { name: 'forbidden' } // 403 }

// Step 4: allow navigation return true }) \n\nIni meng-cover sebagian besar kasus tanpa menyebarkan pengecekan ke banyak komponen.\n\n### Ketika `beforeEnter` lebih tepat\n\nGunakan `beforeEnter` ketika aturan benar-benar spesifik route, seperti “hanya pemilik tiket yang bisa membuka halaman ini” dan bergantung pada `to.params.id`. Buat singkat dan gunakan kembali auth store yang sama agar perilaku konsisten.\n\n## Redirect aman tanpa membuka celah\n\nRedirect bisa diam-diam menggagalkan kontrol akses jika diperlakukan sebagai tepercaya.\n\nPolanya umum adalah: ketika pengguna belum login, kirim mereka ke login dan sertakan query param `returnTo`. Setelah login, baca dan navigasikan ke sana. Risikonya adalah open redirect (mengirim pengguna ke tempat yang tidak diinginkan) dan loop.\n\nJaga perilaku tetap sederhana:\n\n- Pengguna yang belum login pergi ke `Login` dengan `returnTo` di-set ke path saat ini.\n- Pengguna yang sudah login tapi tidak berwenang pergi ke halaman `Forbidden` khusus (bukan `Login`).\n- Hanya izinkan nilai `returnTo` internal yang Anda kenali.\n- Tambahkan satu pemeriksaan loop agar Anda tidak pernah mengarahkan ke tempat yang sama.\n\njs const allowedReturnTo = (to) => { if (!to || typeof to !== 'string') return null if (!to.startsWith('/')) return null // optional: only allow known prefixes if (!['/app', '/admin', '/tickets'].some(p => to.startsWith(p))) return null return to }

router.beforeEach((to) => { if (!auth.isReady) return false

if (!auth.isLoggedIn && to.name !== 'Login') { return { name: 'Login', query: { returnTo: to.fullPath } } }

if (auth.isLoggedIn && !canAccess(to, auth.user) && to.name !== 'Forbidden') { return { name: 'Forbidden' } } }) \n\n## Hindari bocornya data terbatas selama navigasi\n\nKebocoran termudah adalah memuat data sebelum Anda tahu pengguna diizinkan melihatnya.\n\nDi Vue, ini sering terjadi ketika sebuah halaman mengambil data di `setup()` dan router guard berjalan sesaat kemudian. Bahkan jika pengguna diarahkan ulang, respons mungkin masih masuk ke store bersama atau tampil sebentar.\n\nAturan yang lebih aman: otorisasi dulu, lalu muat data.\n\njs // router guard: authorize before entering the route router.beforeEach(async (to) => { await auth.ready() // ensure roles are known const required = to.meta.requiredRole if (required && !auth.hasRole(required)) { return { name: 'forbidden' } } }) ```\n\nJuga waspadai request terlambat ketika navigasi berubah cepat. Batalkan request (mis. dengan AbortController) atau abaikan respons terlambat dengan memeriksa request id.\n\nCaching adalah jebakan umum lainnya. Jika Anda menyimpan “last loaded customer record” secara global, respons khusus admin dapat ditampilkan kemudian kepada non-admin yang mengunjungi shell layar yang sama. Key cache menurut user id dan role, dan bersihkan modul sensitif saat logout (atau saat role berubah).\n\nBeberapa kebiasaan mencegah sebagian besar kebocoran:\n\n- Jangan fetch data sensitif sampai otorisasi dikonfirmasi.\n- Kunci data cache menurut user dan role, atau simpan lokal di halaman.\n- Batalkan atau abaikan request yang sedang berjalan saat route berubah.\n\n## Fallback ramah: 401, 403, dan not found\n\nJalur “tidak” sama pentingnya dengan jalur “ya”. Halaman fallback yang baik menjaga pengguna tetap orientasi dan mengurangi permintaan dukungan.\n\n### 401: Login diperlukan (belum terautentikasi)\n\nGunakan 401 ketika pengguna belum masuk. Sampaikan pesan singkat: mereka perlu login untuk melanjutkan. Jika Anda mendukung kembali ke halaman asli setelah login, validasi path return agar tidak mengarah keluar dari aplikasi Anda.\n\n### 403: Akses ditolak (sudah terautentikasi, tapi tidak diizinkan)\n\nGunakan 403 ketika pengguna sudah masuk tetapi tidak memiliki izin. Jaga pesan netral dan hindari memberi petunjuk detail sensitif.\n\nHalaman 403 yang baik biasanya memiliki judul jelas ("Akses ditolak"), satu kalimat penjelasan, dan langkah aman berikutnya (kembali ke dashboard, hubungi admin, atau ganti akun jika didukung).\n\n### 404: Tidak ditemukan\n\nTangani 404 terpisah dari 401/403. Jika tidak, orang mengira mereka tidak punya izin padahal halaman memang tidak ada.\n\n## Kesalahan umum yang merusak kontrol akses\n\nSebagian besar bug kontrol akses adalah slip logika sederhana yang muncul sebagai loop redirect, kilasan halaman yang salah, atau pengguna yang terjebak.\n\nPenyebab umum:\n\n- Menganggap UI tersembunyi sebagai “keamanan”. Selalu terapkan role di router dan di API.\n- Membaca role dari state usang setelah logout/login.\n- Mengarahkan pengguna yang tidak berwenang ke route lain yang juga terlindungi (loop instan).\n- Mengabaikan momen “auth masih memuat” saat refresh.\n- Mencampur 401 dan 403, yang membingungkan pengguna.\n\nContoh realistis: seorang agen support logout dan seorang admin login di komputer bersama. Jika guard membaca role yang dicache sebelum sesi baru dikonfirmasi, Anda bisa memblokir admin secara keliru atau, lebih buruk, sesaat mengizinkan akses yang seharusnya tidak boleh.\n\n## Daftar periksa cepat sebelum rilis\n\nLakukan pemeriksaan singkat yang fokus pada momen di mana kontrol akses biasanya gagal: jaringan lambat, sesi kadaluwarsa, dan URL yang di-bookmark.\n\n- Setiap route terlindungi memiliki meta requirement eksplisit.\n- Guards menangani state loading auth tanpa menampilkan UI terlindungi.\n- Pengguna tidak berwenang mendarat di halaman 403 yang jelas (bukan bounce membingungkan ke home).\n- Setiap redirect “kembali ke” divalidasi dan tidak bisa membuat loop.\n- Panggilan API sensitif dijalankan hanya setelah otorisasi dikonfirmasi.\n\nLalu uji satu skenario end-to-end: buka URL terlindungi di tab baru saat belum login, login sebagai pengguna dasar, dan pastikan Anda mendarat di halaman yang dimaksud (jika diizinkan) atau 403 yang bersih dengan langkah selanjutnya.\n\n## Contoh: akses support vs admin di aplikasi web kecil\n\nBayangkan aplikasi helpdesk dengan dua role: support dan admin. Support bisa membaca dan membalas tiket. Admin bisa juga, plus mengelola billing dan pengaturan perusahaan.\n\n- /tickets/:id diizinkan untuk support dan admin\n- /settings/billing hanya untuk admin\n\nSekarang momen umum: seorang agen support membuka deep link ke /settings/billing dari bookmark lama. Guard harus memeriksa route meta sebelum halaman dimuat dan memblokir navigasi. Karena pengguna sudah login tapi tidak punya role, mereka harus mendarat di fallback aman (403).\n\nDua pesan yang penting:\n\n- Login required (401): “Silakan masuk untuk melanjutkan.”\n- Access denied (403): “Anda tidak memiliki akses ke Billing Settings.”\n\nYang tidak boleh terjadi: komponen billing dimount, atau data billing di-fetch, bahkan sebentar.\n\nPerubahan role di tengah sesi adalah edge case lain. Jika seseorang dipromosikan atau diturunkan, jangan mengandalkan menu. Periksa ulang role saat navigasi dan putuskan bagaimana menangani halaman aktif: refresh state auth saat profil berubah, atau deteksi perubahan role dan arahkan keluar dari halaman yang tidak lagi diizinkan.\n\n## Langkah selanjutnya: jaga aturan akses tetap terawat\n\nSetelah guards bekerja, risiko lebih besar adalah drift: route baru dirilis tanpa meta, role berganti nama, dan aturan jadi tidak konsisten.\n\nUbah aturan Anda menjadi rencana uji kecil yang bisa dijalankan setiap kali Anda menambah route:\n\n- Sebagai Guest: buka route terlindungi dan konfirmasi Anda mendarat di halaman login tanpa melihat konten parsial.\n- Sebagai User: buka halaman yang seharusnya tidak bisa diakses dan konfirmasi Anda mendapat 403 yang jelas.\n- Sebagai Admin: coba deep link yang disalin dari address bar.\n- Untuk setiap role: refresh pada route terlindungi dan pastikan hasilnya stabil.\n\nJika Anda ingin satu lapisan keamanan tambahan, tambahkan view dev-only atau output console yang mencantumkan route dan requirement meta-nya, sehingga aturan yang hilang terlihat segera.\n\nJika Anda membangun tools internal atau portal dengan AppMaster (appmaster.io), Anda bisa menerapkan pendekatan yang sama: fokuskan route guards pada navigasi di UI Vue3, dan tegakkan permission di tempat logic dan data berada, yaitu backend.\n\nPilih satu perbaikan dan implementasikan secara end-to-end: ketatkan gating fetch data, perbaiki halaman 403, atau kunci penanganan redirect. Perbaikan kecil itulah yang menghentikan sebagian besar bug akses di dunia nyata.

FAQ

Are route guards actually security, or just UX?

Route guards mengontrol navigasi, bukan akses data. Mereka membantu memblokir halaman, mengarahkan ulang, dan menampilkan status 401/403 yang jelas, tetapi mereka tidak bisa menghentikan seseorang memanggil API Anda langsung. Selalu terapkan izin yang sama di backend agar data terbatas tidak pernah dikirimkan.

Why isn’t hiding a menu item enough for role-based access?

Karena menyembunyikan UI hanya mengubah apa yang seseorang lihat, bukan apa yang bisa mereka minta. Pengguna masih bisa mengetik URL, membuka bookmark, atau menavigasi deep link. Anda perlu pemeriksaan router untuk memblokir halaman, dan otorisasi sisi server untuk memblokir data.

What’s a simple roles and permissions model to start with?

Mulailah dengan set kecil yang mudah dipahami, lalu tambahkan permission ketika benar-benar diperlukan. Baseline umum adalah admin, support, dan viewer, lalu tambahkan permission seperti tickets:read atau audit:read untuk tindakan spesifik. Pisahkan antara “siapa Anda” (role) dan “apa yang bisa Anda lakukan” (permission).

How should I use Vue Router meta for access control?

Taruh aturan akses di meta pada record route, seperti requiresAuth, roles, dan permissions. Ini menempatkan aturan dekat dengan halaman yang dilindungi dan membuat guard global Anda jadi bisa diprediksi. Untuk nested route, periksa semua record yang cocok sehingga requirement parent tidak terlewati.

How do I handle nested routes so children inherit parent restrictions?

Baca dari to.matched dan gabungkan requirement dari semua matched route record. Dengan begitu child route tidak bisa melewati requiresAuth atau roles milik parent. Putuskan aturan merge di awal (biasanya: requirement parent berlaku untuk children).

How do I stop redirect loops and “flashes” of protected pages on refresh?

Karena guard bisa berjalan sebelum aplikasi mengetahui siapa pengguna. Perlakukan auth sebagai tiga state—unknown, logged out, logged in—dan jangan pernah mengevaluasi peran saat auth berstatus unknown. Buat guard menunggu inisialisasi tunggal (mis. satu panggilan "who am I") sebelum mengambil keputusan.

When should I use a global beforeEach guard vs beforeEnter?

Gunakan global beforeEach untuk aturan konsisten seperti “memerlukan login” dan “memerlukan role/permission”. Gunakan beforeEnter hanya jika aturan benar-benar spesifik route dan bergantung pada params (mis. “hanya pemilik tiket yang bisa membuka halaman ini”). Pastikan kedua jalur menggunakan sumber kebenaran auth yang sama.

How do I do post-login redirects without creating open redirect holes?

Anggap returnTo sebagai input yang tidak tepercaya. Hanya izinkan path internal yang Anda kenali (mis. nilai yang dimulai dengan / dan cocok dengan prefix yang dikenal), dan tambahkan pemeriksaan loop sehingga Anda tidak mengarahkan kembali ke route yang sama yang terblokir. Pengguna yang belum login pergi ke Login; pengguna yang sudah login tapi tidak berwenang pergi ke halaman 403 khusus.

How do I avoid leaking restricted data during navigation?

Otorisasi dulu, baru fetch. Jika sebuah halaman mengambil data di setup() dan Anda mengarahkan ulang beberapa saat kemudian, respons bisa tetap masuk ke store atau tampil sebentar. Gating untuk request sensitif harus menunggu otorisasi terkonfirmasi, dan batalkan atau abaikan request yang sedang berjalan saat navigasi berubah.

What’s the correct way to use 401 vs 403 vs 404 in a Vue app?

Gunakan 401 saat pengguna belum masuk, dan 403 saat mereka sudah masuk tetapi tidak diizinkan. Jaga 404 terpisah sehingga pengguna tidak mengira mereka tidak memiliki izin untuk route yang sebenarnya tidak ada. Fallback yang jelas dan konsisten mengurangi kebingungan dan tiket dukungan.

Mudah untuk memulai
Ciptakan sesuatu yang menakjubkan

Eksperimen dengan AppMaster dengan paket gratis.
Saat Anda siap, Anda dapat memilih langganan yang tepat.

Memulai