Skip to content

docs: document RequireAuth per-route usage pattern #73

@eugenioenko

Description

@eugenioenko

Summary

Document that RequireAuth (and equivalent guards in other framework adapters) should be used in a way that causes it to re-mount on navigation, not as a persistent layout wrapper that stays mounted across route changes.

This is a documentation-only change — the library doesn't need code changes.

Problem

When RequireAuth is used as a layout wrapper (e.g., wrapping a React Router <Outlet />), it stays mounted across navigations. Token expiration is checked via isExpiredAt() at render time, but since nothing triggers a re-render on navigation, the expiration check doesn't re-run. This means an expired token isn't detected until an API call returns 401.

This is a consequence of the library being router-agnosticRequireAuth doesn't subscribe to route changes, so it can't detect navigation.

Correct patterns (React/Preact)

Option A: Per-route wrapping

Use RequireAuth at the route level so it mounts/unmounts on each navigation:

// ✅ Correct — RequireAuth mounts fresh on each navigation
<Route path="clients" element={<RequireAuth><ClientsPage /></RequireAuth>} />
<Route path="users" element={<RequireAuth><UsersPage /></RequireAuth>} />

// ❌ Avoid — RequireAuth stays mounted, won't re-check expiration on navigation
<Route element={<RequireAuth><Outlet /></RequireAuth>}>
  <Route path="clients" element={<ClientsPage />} />
  <Route path="users" element={<UsersPage />} />
</Route>

Option B: Key-based remount with a layout wrapper

Use location.pathname as a key to force RequireAuth to remount on navigation. This allows using a single wrapper while keeping the library router-agnostic (the router coupling stays in consumer code):

import { useLocation, Outlet } from 'react-router';
import { RequireAuth } from 'oidc-js-react';

function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const location = useLocation();
  return <RequireAuth key={location.pathname}>{children}</RequireAuth>;
}

// Usage — single wrapper, no per-route boilerplate
<Route element={<ProtectedRoute><AdminLayout /></ProtectedRoute>}>
  <Route index element={<DashboardPage />} />
  <Route path="clients" element={<ClientsPage />} />
</Route>

There is no flash of fallback on navigation when the token is valid — RequireAuth reads from the existing auth context on mount and renders children immediately.

Why this is needed

React (and Preact) optimize rendering by skipping components whose props/state/context haven't changed. Since RequireAuth only subscribes to the auth context (not the router context), and the auth state doesn't change when a token passively expires (expiresAt is the same number — only Date.now() moves past it), React reuses the last render output without re-evaluating isExpiredAt().

Other frameworks (Svelte, Vue, Angular, Lit) re-evaluate template expressions naturally on navigation, so this issue is React/Preact-specific.

Scope

  • Update README / docs for oidc-js-react and oidc-js-preact
  • No code changes needed in the library

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions