【React Router】認証状態・権限に応じてアクセス制御する

  • React Routerで特定のページにアクセス制限を設定したい
    • ログインしていない場合、ログインページにリダイレクト
    • 権限が無い場合、アクセスできない (403ページを表示する)
    • 存在しないページの場合、アクセスできない (404ページを表示する)
  • 権限はUserAdminの2種類
    • Adminのみ/dashboardにアクセスできる
    • その他のページは権限問わずアクセスできる

1. 定数を定義

constants.ts
export const PATHS = {
HOME: '/',
DASHBOARD: '/dashboard',
POSTS: '/posts/:postId',
SIGNIN: 'signin',
} as const satisfies Record<string, `/${string}`>;
export const ROLES = {
USER: 'user',
ADMIN: 'admin',
} as const satisfies Record<string, string>;
export type Role = (typeof ROLES)[keyof typeof ROLES];

2. 権限毎にRouterを実装

components/AdminRouter.tsx
import { Route, Routes } from 'react-router-dom';
import { PATHS } from '@/constants';
export default function AdminRouter() {
return (
<Routes>
<Route path={PATHS.HOME} element={<HomePage />} />
<Route path={PATHS.DASHBOARD} element={<DashboardPage />} />
<Route path={PATHS.POSTS} element={<PostsPage />} />
<Route path="*" element={<UnauthorizedPage />} />
</Routes>
);
}
components/UserRouter.tsx
import { Route, Routes } from 'react-router-dom';
import { PATHS } from '@/constants';
export default function UserRouter() {
return (
<Routes>
<Route path={PATHS.HOME} element={<HomePage />} />
<Route path={PATHS.POSTS} element={<PostsPage />} />
<Route path="*" element={<UnauthorizedPage />} />
</Routes>
);
}
  • 権限毎にRouterを実装
    • React Routerの仕様上、同一パスに対して権限毎のアクセス制限ができないため
  • Adminのみ’/dashboard’のパスを追加
  • 想定しないパス (*) の時は403ページを表示
    • これによりUserは’/dashboard’にアクセスできない
    • 存在しないページ (404) の場合は後述のRootRouter.tsx内で制御する
components/RootRouter.tsx
import type { ReactElement } from 'react';
import { matchPath, useLocation } from 'react-router-dom';
import { AdminRouter } from '@/components/AdminRouter';
import { UserRouter } from '@/components/UserRouter';
import { PATHS, type Router } from '@/constants';
const routerMap = {
admin: <AdminRouter />,
user: <UserRouter />,
} satisfies Record<Role, ReactElement>;
export default function RootRouter() {
const { pathname } = useLocation();
const { isAuthenticated, role } = useAuth(); // 認証状態を返すダミーHook
// 存在するパスかどうかを判定
const existsPath = Object.values(PATHS).some(
(path) => !!matchPath(path, pathname),
);
if (!existsPath) return <NotFoundPage />;
if (!isAuthenticated) return <Navigate to={PATH.SIGNIN} />;
return routerMap;
}
  • 存在するパスかをmatchPath()で判定し、否であれば404ページを表示
  • ログインしてなければログインページにリダイレクト
  • 最終的に、該当する権限のRouterをレンダリング