Skip to main content

React

Official

Hooks and components to drop ThunderID into a React + Vite app in minutes. The provider restores an existing session on mount, handles the redirect callback, and refreshes tokens in the background, so your components only ever read state.

@thunderid/react
Apache 2.0 licence
API referenceQuickstartSource

Protecting routes

~19 min

Securing 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.

1

Installation

bash
npm install @thunderid/react-router
2

Basic Setup with ProtectedRoute

src/App.tsx
tsx

function App() {
  return (
    <ThunderIDProvider
      baseUrl="https://localhost:8090"
      clientId="your-client-id"
    >
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<div>Public Home Page</div>} />
          <Route path="/signin" element={<SignIn />} />
          <Route
            path="/dashboard"
            element={
              <ProtectedRoute redirectTo="/signin">
                <Dashboard />
              </ProtectedRoute>
            }
          />
          <Route
            path="/profile"
            element={
              <ProtectedRoute redirectTo="/signin">
                <Profile />
              </ProtectedRoute>
            }
          />
        </Routes>
      </BrowserRouter>
    </ThunderIDProvider>
  )
}

export default App
3

Custom Fallback and Loading States

You can customize the behavior of `ProtectedRoute` with custom fallback components and loading states:

src/App.tsx
tsx

// Redirect to custom login page
<Route
  path="/dashboard"
  element={
    <ProtectedRoute redirectTo="/login">
      <Dashboard />
    </ProtectedRoute>
  }
/>

// Custom fallback component
<Route
  path="/dashboard"
  element={
    <ProtectedRoute
      fallback={
        <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
<Route
  path="/dashboard"
  element={
    <ProtectedRoute redirectTo="/signin" loader={<div className="spinner">Loading...</div>}>
      <Dashboard />
    </ProtectedRoute>
  }
/>
4

Integration with Layouts

Protect multiple routes using a shared layout:

src/App.tsx
tsx

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 />}>
          <Route
            path="dashboard"
            element={
              <ProtectedRoute redirectTo="/signin">
                <Dashboard />
              </ProtectedRoute>
            }
          />
          <Route
            path="profile"
            element={
              <ProtectedRoute redirectTo="/signin">
                <Profile />
              </ProtectedRoute>
            }
          />
          <Route
            path="settings"
            element={
              <ProtectedRoute redirectTo="/signin">
                <Settings />
              </ProtectedRoute>
            }
          />
        </Route>
      </Routes>
    </BrowserRouter>
  )
}
1

Installation

bash
npm install @thunderid/tanstack-router
2

Basic Setup with ProtectedRoute

src/App.tsx
tsx

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 (
    <ThunderIDProvider
      baseUrl="https://localhost:8090"
      clientId="your-client-id"
    >
      <RouterProvider router={router} />
    </ThunderIDProvider>
  )
}

export default App
3

Custom Fallback and Loading States

src/routes.tsx
tsx

// Redirect to custom login page
const dashboardRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/dashboard',
  component: () => (
    <ProtectedRoute redirectTo="/login">
      <Dashboard />
    </ProtectedRoute>
  ),
})

// Custom fallback component
const dashboardRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/dashboard',
  component: () => (
    <ProtectedRoute
      fallback={
        <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
const dashboardRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/dashboard',
  component: () => (
    <ProtectedRoute redirectTo="/signin" loader={<div className="spinner">Loading...</div>}>
      <Dashboard />
    </ProtectedRoute>
  ),
})
4

Integration with Layouts

src/App.tsx
tsx

const rootRoute = createRootRoute({
  component: () => <div>Root Layout</div>,
})

// Public routes
const 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 layout
const 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 (
    <ThunderIDProvider
      baseUrl="https://localhost:8090"
      clientId="your-client-id"
    >
      <RouterProvider router={router} />
    </ThunderIDProvider>
  )
}

export default App
1

Basic Custom Route Guard

src/App.tsx
tsx

const ProtectedRoute = ({ children }: { children: ReactNode }) => {
  const { isSignedIn } = useThunderID()

  if (!isSignedIn) {
    return <Unauthenticated />
  }

  return children
}

const App = () => {
  return (
    <Routes>
      <Route
        path="/contact"
        element={
          <ProtectedRoute>
            <Contact />
          </ProtectedRoute>
        }
      />
      <Route path="/" element={<Home />} />
      <Route path="*" element={<PageNotFound />} />
    </Routes>
  )
}

export default App
2

Advanced Custom Implementation

You can extend the basic pattern to include loading states, redirects, and custom logic:

src/components/ProtectedRoute.tsx
tsx

interface ProtectedRouteProps {
  children: ReactNode
  redirectTo?: string
  fallback?: ReactNode
  requireRole?: string
}

export const ProtectedRoute = ({
  children,
  redirectTo = '/signin',
  fallback,
  requireRole,
}: ProtectedRouteProps) => {
  const { isSignedIn, user, loading } = useThunderID()

  // Show loading state while authentication is being checked
  if (loading) {
    return <div>Loading...</div>
  }

  // Redirect to sign-in if not authenticated
  if (!isSignedIn) {
    if (fallback) {
      return <>{fallback}</>
    }
    return <Navigate to={redirectTo} replace />
  }

  // Check for required role if specified
  if (requireRole && user?.roles && !user.roles.includes(requireRole)) {
    return <div>You don't have permission to access this page</div>
  }

  return <>{children}</>
}
ThunderID LogoThunderID Logo

Product

DocsAPIsSDKs
© Copyright Linux Foundation Europe.For web site terms of use, trademark policy and other project policies please see https://linuxfoundation.eu/en/policies.