360 Secure SkillHub
360 安全技能中心

Route Protection

本技能提供前端路由保护方案,覆盖React Router、Next.js、Vue Router和Nuxt。核心功能:定义鉴权状态,实现布局守卫与导航守卫,处理401/403跳转、登录回跳、会话过期和RBAC权限校验。典型场景:需登录页面、多角色权限路由、租户切换、统一登录态。输入路由配置与权限要求,输出可复用守卫组件、中间件及安全约束。前端保护仅提升体验,不能替代服务端授权。

bovinphang
bovinphang
热度 48.28
最近更新 2026-09-27 11:40:06
分类:frontend-route-protection应用场景:route-protection / auth-guard / RBAC / permission-routes ...

安装技能

复制下面的话术发给你的 Agent,它会通过 secure-skills 检索并安装该技能。

执行命令安装此 skill: npx secure-skills-cli add fec-route-protection
Metadata
name
fec-route-protection
description
Use when implementing or reviewing frontend route protection, auth guards, RBAC, permission routes, login state handling, redirects, middleware, React Router, Next.js, Vue Router, or Nuxt route middleware; Chinese triggers include route protection, permission routing, login state.

#Route protection

Purpose

Establish clear authentication, authorization and redirection boundaries for front-end applications to avoid unauthorized access and flickering rendering.

When to Use

  • The page needs to be logged in to access, or different roles can see different routes.
  • Requires implementing route guards for React Router, Next.js, Nuxt or Vue Router.
  • Login expiration, insufficient permissions, and organization/tenant switching need to be handled uniformly.
  • Not used to replace server-side authorization; front-end routing protection can only improve the experience and cannot be used as the only security boundary.

Procedure

1. Define authentication and authorization status

export type AuthStatus = "loading" | "anonymous" | "authenticated";

export interface CurrentUser {
  id: string;
  roles: string[];
  permissions: string[];
}

export function canAccess(user: CurrentUser, required: string[]) {
  return required.every((permission) => user.permissions.includes(permission));
}

2. React Router uses layout guards

import { Navigate, Outlet, useLocation } from "react-router-dom";

interface ProtectedRouteProps {
  requiredPermissions?: string[];
}

export function ProtectedRoute({ requiredPermissions = [] }: ProtectedRouteProps) {
  const location = useLocation();
  const { status, user } = useAuth();

  if (status === "loading") return <RouteLoading />;
  if (status === "anonymous") {
    return <Navigate to="/login" replace state={{ from: location }} />;
  }
  if (requiredPermissions.length > 0 && !canAccess(user, requiredPermissions)) {
    return <Navigate to="/403" replace />;
  }

  return <Outlet />;
}
const router = createBrowserRouter([
  {
    element: <ProtectedRoute requiredPermissions={["orders:read"]} />,
    children: [{ path: "/orders", element: <OrdersPage /> }],
  },
]);

3. Next.js prioritizes processing at the server boundary

// middleware.ts
import { NextResponse, type NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const token = request.cookies.get("session")?.value;
  const isPrivateRoute = request.nextUrl.pathname.startsWith("/dashboard");

  if (isPrivateRoute && !token) {
    const loginUrl = new URL("/login", request.url);
    loginUrl.searchParams.set("redirect", request.nextUrl.pathname);
    return NextResponse.redirect(loginUrl);
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/dashboard/:path*"],
};

For App Router pages that require fine-grained permissions, re-verify permissions in the server component or server action, regardless of client status.

4. Vue Router / Nuxt uses navigation guards

router.beforeEach(async (to) => {
  const auth = useAuthStore();
  if (auth.status === "unknown") await auth.fetchCurrentUser();

  if (to.meta.requiresAuth && !auth.user) {
    return { path: "/login", query: { redirect: to.fullPath } };
  }

  const required = to.meta.permissions as string[] | undefined;
  if (required?.length && !auth.canAccess(required)) {
    return { path: "/403" };
  }
});

5. Unify failure and jump experience

  • 401: Clean up expired sessions, jump to the login page and keep redirect.
  • 403: Enter the unauthorized page, do not try again.
  • loading: Render a stable skeleton screen to avoid displaying private content first and then jumping.
  • Successful login: only jump to the same origin and allowed path to prevent open redirect.
  • RBAC: The permission matrix is returned from the trusted session or backend; the frontend is only used for routing experience and UI tailoring, and cannot replace interface authorization.
  • Session refresh: Uniformly clean up status after 401 refresh failure; avoid multiple concurrent requests from triggering jumps or refresh storms.

Constraints

  • Front-end routing protection is not a security boundary; API, SSR loader, and server actions must undergo repeated authorization verification.
  • Do not use useEffect to jump to the private page after the component is rendered, otherwise sensitive content will flicker.
  • The redirect parameter must be restricted to within-site paths and URL input cannot be trusted directly.
  • Permission information should come from a trusted session or backend response, don't rely solely on role fields in localStorage.
  • Multi-tenant applications must incorporate the tenant/org context into permission determinations.
  • Menu hiding is not considered authorization completion; users must authenticate when accessing the URL directly, refreshing the page, tampering with localStorage, and replacing tenants.

Expected Output

Produce a set of routing guard implementations, covering loading, not logged in, insufficient permissions, bounce after login and session expiration. During verification, directly access the private URL, refresh the page, switch roles, and tamper with redirect parameters to confirm that the behavior is stable and the API still has server authorization.