How the Permission Guard System Operates in the Celeris Web Router
The Celeris Web permission guard system intercepts every navigation event through a global beforeEach hook that validates authentication tokens, checks route whitelists, and dynamically injects authorized routes before allowing the router to resolve the destination.
The Celeris Web frontend implements a robust access-control layer directly within its Vue Router configuration, ensuring that navigation permissions are enforced before any page renders. By centralizing authorization logic in the createPermissionGuard function, the system separates routing concerns from authentication state management while supporting multiple permission modes. This analysis examines the exact source code implementation in the kirklin/celeris-web repository to demonstrate how the guard orchestrates token validation, dynamic route generation, and state cleanup.
Core Architecture and Guard Registration
The permission guard is wired into the application during router initialization. In apps/admin/src/router/index.ts, the router instance is created with static public routes, then immediately passed to setupRouterGuards to register global navigation hooks.
// apps/admin/src/router/index.ts
import { createRouter, createWebHistory } from "vue-router"
import { setupRouterGuards } from "~/router/guard"
import { staticRoutes } from "~/router/routes"
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: staticRoutes,
})
// Register global guards (permission + state cleanup)
setupRouterGuards(router)
export default router
The setupRouterGuards function in apps/admin/src/router/guard/index.ts instantiates both the permission validator and a separate state-management guard.
// apps/admin/src/router/guard/index.ts
import { createPermissionGuard } from "~/router/guard/permissionGuard"
import { createStateGuard } from "~/router/guard/stateGuard"
export const setupRouterGuards = (router: Router) => {
createPermissionGuard(router) // Runs first
createStateGuard(router) // Runs second
}
Inside the Permission Guard Execution Flow
The createPermissionGuard function in apps/admin/src/router/guard/permissionGuard.ts attaches an asynchronous beforeEach handler that executes the following evaluation steps on every navigation:
1. Whitelist Bypass for Public Pages
The guard maintains a whitePathList array that contains paths accessible without authentication (specifically the login page). If the target path is whitelisted, the guard checks whether the user already has a valid token. When an authenticated user attempts to visit the login page, they are redirected to their configured home URL instead of seeing the login form.
2. Token-Based Authentication Check
For non-whitelisted routes, the guard verifies the presence of a token via userStore.getToken. If no token exists and the route's metadata does not contain shouldIgnoreAuth: true, the navigation is aborted and redirected to the login page, preserving the intended destination in a query parameter.
3. Post-Login 404 Resolution
After authentication, if the router would land on the 404 Page Not Found route (which exists in the static route table), the guard automatically redirects to the user's home page to prevent confusing error screens immediately after login.
4. User Info Hydration
If the user store indicates it has never been initialized (updatedAt === 0), the guard calls await userStore.getUserInfoAction() to fetch profile data before proceeding. This ensures that downstream logic has access to role identifiers and permissions.
5. Dynamic Route Injection
The most critical operation occurs on the first visit to a protected route. The guard checks permissionStore.getShouldAddRouteDynamically to determine if routes have already been built. If not, it executes:
const routes = await permissionStore.buildRoutesAction()
routes.forEach((route) => {
router.addRoute(route)
})
router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw)
permissionStore.setShouldAddRouteDynamically(true)
After injection, if the original target was the placeholder 404 route, the guard triggers a secondary navigation to the fullPath, allowing Vue Router to correctly match against the newly added dynamic routes.
Dynamic Route Generation Based on Permission Mode
The buildRoutesAction method in apps/admin/src/store/modules/permission.ts determines which routes to inject based on the application's configured permission strategy:
- ROLE mode: Generates routes from a static mapping filtered by the user's role ID.
- ROUTE_MAPPING mode: Fetches a simplified route list from the backend and maps it to Vue Router objects.
- BACKEND mode: Directly fetches complete route definitions from
/api/permission/routesand returns them asRouteRecordRawobjects.
This architecture allows the same guard logic to support everything from simple role-based filtering to fully server-driven navigation trees without changing the router configuration.
State Cleanup Between Navigations
A separate createStateGuard in apps/admin/src/router/guard/stateGuard.ts runs before each route change to prevent permission state from leaking between pages. When the path actually changes (not just query parameters), it resets both the permission and user stores:
// apps/admin/src/router/guard/stateGuard.ts
export const createStateGuard = (router: Router) => {
router.beforeEach((to, from, next) => {
if (from.path !== to.path) {
const permissionStore = usePermissionStore()
const userStore = useUserStore()
permissionStore.resetPermissionState()
userStore.resetUserState()
}
next()
})
}
This ensures that stale permission codes or user data do not persist when navigating to a new section of the application.
Summary
- The permission guard is registered via
setupRouterGuardsimmediately after router creation inapps/admin/src/router/index.ts. - A whitelist check allows public pages (like login) to bypass authentication verification entirely.
- Unauthenticated users attempting to access protected routes are redirected to login with a preserved
redirectquery parameter. - On first navigation to a protected area,
buildRoutesActiongenerates routes based on the configured permission mode (ROLE, ROUTE_MAPPING, or BACKEND) and injects them usingrouter.addRoute. - A companion state guard resets Pinia stores whenever the navigation path changes, preventing data leakage between routes.
- All authorization logic is centralized in
apps/admin/src/router/guard/permissionGuard.tswhile state management resides inapps/admin/src/store/modules/permission.tsandapps/admin/src/store/modules/user.ts.
Frequently Asked Questions
What happens if a user navigates to a protected route without an authentication token?
The guard detects the missing token via userStore.getToken and checks the route's shouldIgnoreAuth metadata. If the flag is not present, the navigation is cancelled and the user is redirected to the login page with a redirect query parameter containing the original target path, allowing seamless return after authentication.
How does the system handle dynamic route generation for different user roles?
On the first protected navigation, the guard calls permissionStore.buildRoutesAction(), which inspects appStore.getProjectSetting.permissionMode to determine whether to generate routes from static role mappings, backend route mappings, or a direct API endpoint. The resulting RouteRecordRaw array is then injected into the router using router.addRoute() for each entry.
Why does the permission guard redirect to the home page after login when landing on a 404 route?
Immediately after authentication, if the router resolves to the static 404 catch-all route (indicating the requested path does not exist in the static route table), the guard assumes the user attempted to deep-link to a dynamic route that hasn't been built yet. Rather than showing an error page, it redirects to the user's configured home URL to provide a graceful landing point.
What is the purpose of the state guard alongside the permission guard?
While the permission guard handles authorization logic, the state guard in apps/admin/src/router/guard/stateGuard.ts focuses on data hygiene. It resets the permission and user Pinia stores whenever the navigation path changes, ensuring that permissions and user data from a previous session or page do not incorrectly influence the new page's behavior.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →