Protecting routes
~20 minSecuring routes protects sensitive data and keeps parts of the application to signed-in users. Pick the router your application uses, or build your own guard from the SDK primitives.
Set up route protection
The official `@thunderid/react-router` package provides a `ProtectedRoute` component that guards a route and sends unauthenticated visitors to sign-in.
Installation
npm install @thunderid/react-router
Basic Setup with ProtectedRoute
function App() {return (<ThunderIDProviderbaseUrl="https://localhost:8090"clientId="your-client-id"><BrowserRouter><Routes><Route path="/" element={<div>Public Home Page</div>} /><Route path="/signin" element={<SignIn />} /><Routepath="/dashboard"element={<ProtectedRoute redirectTo="/signin"><Dashboard /></ProtectedRoute>}/><Routepath="/profile"element={<ProtectedRoute redirectTo="/signin"><Profile /></ProtectedRoute>}/></Routes></BrowserRouter></ThunderIDProvider>)}export default App
Custom Fallback and Loading States
You can customize the behavior of `ProtectedRoute` with custom fallback components and loading states:
// Redirect to custom login page<Routepath="/dashboard"element={<ProtectedRoute redirectTo="/login"><Dashboard /></ProtectedRoute>}/>// Custom fallback component<Routepath="/dashboard"element={<ProtectedRoutefallback={<div className="auth-required"><h2>Please sign in</h2><p>You need to be signed in to access this page.</p></div>}><Dashboard /></ProtectedRoute>}/>// Custom loading state<Routepath="/dashboard"element={<ProtectedRoute redirectTo="/signin" loader={<div className="spinner">Loading...</div>}><Dashboard /></ProtectedRoute>}/>
Integration with Layouts
Protect multiple routes using a shared layout:
function App() {return (<BrowserRouter><Routes>{/* Public routes */}<Route path="/" element={<Home />} /><Route path="/about" element={<About />} /><Route path="/signin" element={<SignIn />} />{/* Protected routes with layout */}<Route path="/app" element={<AppLayout />}><Routepath="dashboard"element={<ProtectedRoute redirectTo="/signin"><Dashboard /></ProtectedRoute>}/><Routepath="profile"element={<ProtectedRoute redirectTo="/signin"><Profile /></ProtectedRoute>}/><Routepath="settings"element={<ProtectedRoute redirectTo="/signin"><Settings /></ProtectedRoute>}/></Route></Routes></BrowserRouter>)}
Installation
npm install @thunderid/tanstack-router
Basic Setup with ProtectedRoute
const rootRoute = createRootRoute({component: () => <div>Root Layout</div>,})const indexRoute = createRoute({getParentRoute: () => rootRoute,path: '/',component: () => <div>Public Home Page</div>,})const signinRoute = createRoute({getParentRoute: () => rootRoute,path: '/signin',component: SignIn,})const dashboardRoute = createRoute({getParentRoute: () => rootRoute,path: '/dashboard',component: () => (<ProtectedRoute redirectTo="/signin"><Dashboard /></ProtectedRoute>),})const profileRoute = createRoute({getParentRoute: () => rootRoute,path: '/profile',component: () => (<ProtectedRoute redirectTo="/signin"><Profile /></ProtectedRoute>),})const routeTree = rootRoute.addChildren([indexRoute, signinRoute, dashboardRoute, profileRoute])const router = createRouter({ routeTree })function App() {return (<ThunderIDProviderbaseUrl="https://localhost:8090"clientId="your-client-id"><RouterProvider router={router} /></ThunderIDProvider>)}export default App
Custom Fallback and Loading States
// Redirect to custom login pageconst dashboardRoute = createRoute({getParentRoute: () => rootRoute,path: '/dashboard',component: () => (<ProtectedRoute redirectTo="/login"><Dashboard /></ProtectedRoute>),})// Custom fallback componentconst dashboardRoute = createRoute({getParentRoute: () => rootRoute,path: '/dashboard',component: () => (<ProtectedRoutefallback={<div className="auth-required"><h2>Please sign in</h2><p>You need to be signed in to access this page.</p></div>}><Dashboard /></ProtectedRoute>),})// Custom loading stateconst dashboardRoute = createRoute({getParentRoute: () => rootRoute,path: '/dashboard',component: () => (<ProtectedRoute redirectTo="/signin" loader={<div className="spinner">Loading...</div>}><Dashboard /></ProtectedRoute>),})
Integration with Layouts
const rootRoute = createRootRoute({component: () => <div>Root Layout</div>,})// Public routesconst indexRoute = createRoute({getParentRoute: () => rootRoute,path: '/',component: Home,})const aboutRoute = createRoute({getParentRoute: () => rootRoute,path: '/about',component: About,})const signinRoute = createRoute({getParentRoute: () => rootRoute,path: '/signin',component: SignIn,})// Protected routes with layoutconst appLayoutRoute = createRoute({getParentRoute: () => rootRoute,path: '/app',component: AppLayout,})const appDashboardRoute = createRoute({getParentRoute: () => appLayoutRoute,path: '/dashboard',component: () => (<ProtectedRoute redirectTo="/signin"><Dashboard /></ProtectedRoute>),})const appProfileRoute = createRoute({getParentRoute: () => appLayoutRoute,path: '/profile',component: () => (<ProtectedRoute redirectTo="/signin"><Profile /></ProtectedRoute>),})const appSettingsRoute = createRoute({getParentRoute: () => appLayoutRoute,path: '/settings',component: () => (<ProtectedRoute redirectTo="/signin"><Settings /></ProtectedRoute>),})const routeTree = rootRoute.addChildren([indexRoute,aboutRoute,signinRoute,appLayoutRoute.addChildren([appDashboardRoute, appProfileRoute, appSettingsRoute]),])const router = createRouter({ routeTree })function App() {return (<ThunderIDProviderbaseUrl="https://localhost:8090"clientId="your-client-id"><RouterProvider router={router} /></ThunderIDProvider>)}export default App
Basic Custom Route Guard
const ProtectedRoute = ({ children }: { children: ReactNode }) => {const { isSignedIn } = useThunderID()if (!isSignedIn) {return <Unauthenticated />}return children}const App = () => {return (<Routes><Routepath="/contact"element={<ProtectedRoute><Contact /></ProtectedRoute>}/><Route path="/" element={<Home />} /><Route path="*" element={<PageNotFound />} /></Routes>)}export default App
Advanced Custom Implementation
You can extend the basic pattern to include loading states, redirects, and custom logic:
interface ProtectedRouteProps {children: ReactNoderedirectTo?: stringfallback?: ReactNoderequireRole?: string}export const ProtectedRoute = ({children,redirectTo = '/signin',fallback,requireRole,}: ProtectedRouteProps) => {const { isSignedIn, user, loading } = useThunderID()// Show loading state while authentication is being checkedif (loading) {return <div>Loading...</div>}// Redirect to sign-in if not authenticatedif (!isSignedIn) {if (fallback) {return <>{fallback}</>}return <Navigate to={redirectTo} replace />}// Check for required role if specifiedif (requireRole && user?.roles && !user.roles.includes(requireRole)) {return <div>You don't have permission to access this page</div>}return <>{children}</>}