Route Guards
12.2.1. Global Guards
- Global guards must be registered using
router.beforeEach. - In Vue Router 4, return values must be used instead of the
next()callback.next()must not be used. - To allow navigation, return
trueorundefined. - To block navigation, return
falseor a redirect route.
typescript
// src/router/index.ts
router.beforeEach((to, from) => {
// Allow pages that do not require authentication
if (!to.meta.requiresAuth) {
return true
}
const authStore = useAuthStore()
if (!authStore.isAuthenticated) {
return { name: 'Login', query: { redirect: to.fullPath } }
}
return true
})router.afterEachis used for setting page titles, sending analytics events, and similar tasks.
typescript
router.afterEach((to) => {
document.title = `${to.meta.title} | TQS`
})12.2.2. Per-Route Guards
- Guards that apply only to specific routes must use the
beforeEnterproperty. - Role-based access control must be implemented using per-route guards in conjunction with
meta.roles. - When guard logic becomes complex, it should be extracted into a separate function.
typescript
// src/router/guards/roleGuard.ts
import type { NavigationGuardWithThis } from 'vue-router'
import { useAuthStore } from '@/stores/useAuthStore'
export function requireRoles(
allowedRoles: string[]
): NavigationGuardWithThis<undefined> {
return () => {
const authStore = useAuthStore()
const userRole = authStore.user?.role
if (!userRole || !allowedRoles.includes(userRole)) {
return { name: 'Forbidden' }
}
return true
}
}typescript
// src/router/routes.ts
import { requireRoles } from './guards/roleGuard'
{
path: '/admin/dashboard',
name: 'AdminDashboard',
component: () => import('@/views/admin/DashboardView.vue'),
beforeEnter: requireRoles(['admin', 'super-admin']),
meta: { title: 'Admin Dashboard', requiresAuth: true, roles: ['admin'] },
}12.2.3. In-Component Guards
- In-component guards must use the Composition API's
onBeforeRouteLeaveandonBeforeRouteUpdate. - The Options API's
beforeRouteEnterandbeforeRouteLeavemust not be used. onBeforeRouteLeaveis used to warn users about unsaved data when leaving a page.
vue
<script setup lang="ts">
import { ref } from 'vue'
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
const hasUnsavedChanges = ref(false)
onBeforeRouteLeave(() => {
if (hasUnsavedChanges.value) {
const answer = window.confirm('You have unsaved changes. Do you want to leave?')
if (!answer) {
return false
}
}
})
onBeforeRouteUpdate(async (to) => {
// Reload data when only the parameters change within the same component
await fetchData(to.params.id as string)
})
</script>12.2.4. Authentication Guard Implementation
- The authentication guard must operate in the following sequence.
| Step | Action | On Failure |
|---|---|---|
| 1 | Check token existence | Redirect to login page |
| 2 | Validate token | Attempt token refresh |
| 3 | Load user information | Redirect to login page |
| 4 | Verify permissions | Redirect to 403 page |
typescript
// src/router/guards/authGuard.ts
import { useAuthStore } from '@/stores/useAuthStore'
export async function authGuard(to: RouteLocationNormalized) {
if (!to.meta.requiresAuth) {
return true
}
const authStore = useAuthStore()
// Step 1: Check token
if (!authStore.token) {
return { name: 'Login', query: { redirect: to.fullPath } }
}
// Step 2: Validate and refresh token
if (authStore.isTokenExpired) {
const refreshed = await authStore.refreshToken()
if (!refreshed) {
authStore.clearAuth()
return { name: 'Login', query: { redirect: to.fullPath } }
}
}
// Step 3: Load user information
if (!authStore.user) {
try {
await authStore.fetchUser()
} catch {
authStore.clearAuth()
return { name: 'Login', query: { redirect: to.fullPath } }
}
}
// Step 4: Verify permissions
const requiredRoles = to.meta.roles
if (requiredRoles && !requiredRoles.includes(authStore.user.role)) {
return { name: 'Forbidden' }
}
return true
}typescript
// src/router/index.ts
import { authGuard } from './guards/authGuard'
router.beforeEach(authGuard)- The authentication guard must be registered as a single entry point in the global
beforeEach. - When redirecting to the login page, the original path must be preserved in
query.redirect. - On token refresh failure, all stored authentication information must be cleared.