On every project you need to protect routes. Some are for authenticated users only, some for admins, some for guests only. You write a PrivateRoute. Then a role check hook. Then a HasAccess component. You move to the next project — and write it all again.
The logic is always the same. The implementation is always different.
react-protected standardizes this. You describe what each route requires. The library checks access and redirects.
Two packages
@react-protected/core — framework-agnostic guard logic. No dependency on React or any router.
@react-protected/react-router — adapter for React Router. Two styles supported: config-based and JSX.
Config-based
const router = createGuardedRouter(
[
{ path: '/', element: <HomePage /> },
{ path: '/login', element: <LoginPage />, access: 'guest-only' },
{ path: '/dashboard', element: <DashboardPage />, access: 'authenticated' },
{ path: '/admin', element: <AdminPage />, access: 'authenticated', roles: ['admin'] },
{
path: '/contracts',
element: <ContractsPage />,
access: 'authenticated',
permissions: ['contracts:read'],
},
],
{
getUser: () => useAuthStore.getState().user,
hasRole: (user, roles) => roles.some((role) => user.roles.includes(role)),
hasPermission: (user, permissions) =>
permissions.every((p) => user.permissions.includes(p)),
loginPath: '/login',
forbiddenPath: '/403',
}
)
JSX
<GuardProvider
getUser={() => useAuthStore.getState().user}
hasRole={(user, roles) => roles.some((role) => user.roles.includes(role))}
loginPath="/login"
forbiddenPath="/403"
>
<Routes>
<Route path="/" element={<HomePage />} />
<Route
path="/dashboard"
element={
<GuardRoute access="authenticated">
<DashboardPage />
</GuardRoute>
}
/>
</Routes>
</GuardProvider>
Roles, permissions, or both
RBAC — pass roles to the route. You define the check logic yourself: OR, AND, hierarchy — whatever your domain requires.
ABAC — pass permissions. Works the same way.
Both at once — the check passes only when both conditions are met.
Not using React Router?
Use @react-protected/core directly. Here's TanStack Router:
const guard = createGuard({
getUser: () => useAuthStore.getState().user,
hasRole: (user, roles) => roles.some((role) => user.roles.includes(role)),
})
const dashboardRoute = createRoute({
path: '/dashboard',
beforeLoad: ({ location }) => {
const result = guard.check(
{ path: '/dashboard', access: 'authenticated' },
location.pathname
)
if (!result.allowed) throw redirect({ to: result.redirectTo })
},
component: DashboardPage,
})
👉 github.com/astakhovaskold/react-protected
Issues and stars are welcome.
Top comments (0)