# Plan: Replace Custom AuthContext with react-oidc-context ## Goal Replace the hand-rolled `AuthContext.tsx` / `UserManager` setup with `react-oidc-context`, which wraps `oidc-client-ts` and handles token expiry events, silent renew errors, and session monitoring out of the box. ## Current state - `src/Budget.Client/src/auth/authConfig.ts` — `UserManagerSettings` object - `src/Budget.Client/src/auth/AuthContext.tsx` — custom Provider + `useAuth()` hook, module-level `UserManager` - `src/Budget.Client/src/auth/AuthGuard.tsx` — reads from `useAuth()` - `src/Budget.Client/src/pages/CallbackPage.tsx` — calls `userManager.signinRedirectCallback()` - `src/Budget.Client/src/api/client.ts` — `setTokenProvider(fn)` wired in `main.tsx` - `src/Budget.Client/src/main.tsx` — wraps app in `` ## Target state - Remove `AuthContext.tsx`. - `main.tsx` wraps the app in `` from `react-oidc-context` using the existing `authConfig`. - `AuthGuard.tsx` uses `useAuth()` from `react-oidc-context`. - `CallbackPage.tsx` becomes a thin component — `react-oidc-context` handles the callback automatically when `onSigninCallback` is provided to `AuthProvider`. - A `TokenSync` component reads the token from `react-oidc-context` and pushes it into `api/client.ts` via `setTokenProvider`. - `onSigninCallback` navigates to `/` on success but skips navigation when the URL contains `error`. - `CallbackPage` reads `error` / `error_description` from URL params and renders a user-friendly error message. ## Steps ### Phase 1 — Install 1. `npm install react-oidc-context` in `src/Budget.Client/`. ### Phase 2 — Replace AuthProvider 2. Delete `src/Budget.Client/src/auth/AuthContext.tsx`. 3. Update `src/Budget.Client/src/main.tsx`: - Import `AuthProvider` from `react-oidc-context`. - Add `onSigninCallback` that calls `window.history.replaceState({}, '', '/')` unless the URL has `?error=`. - Wrap the app: ``. - Remove the old `` import and `setTokenProvider` wiring. 4. Create `src/Budget.Client/src/auth/TokenSync.tsx`: ```tsx import { useAuth } from 'react-oidc-context'; import { useEffect } from 'react'; import { setTokenProvider } from '../api/client'; export function TokenSync() { const auth = useAuth(); useEffect(() => { setTokenProvider(() => auth.user?.access_token ?? null); }, [auth.user]); return null; } ``` 5. Render `` inside `` in `main.tsx`. ### Phase 3 — Update AuthGuard and CallbackPage 6. Update `AuthGuard.tsx` to use `useAuth` from `react-oidc-context`. The shape is: - `auth.isLoading` — waiting for user to load - `auth.isAuthenticated` — user is signed in - `auth.signinRedirect()` — trigger login 7. Update `CallbackPage.tsx`: - `react-oidc-context` processes the callback automatically; the page just needs to read `?error` / `?error_description` from `useSearchParams()` and render a friendly message if present. - On success the `onSigninCallback` in `main.tsx` navigates away, so this component effectively only shows on error. ### Phase 4 — Clean up 8. Remove `export { userManager }` from the old AuthContext (now deleted) and fix any imports that referenced it (only `CallbackPage` should have used it). 9. Run `npm run build` — confirm zero TypeScript errors. ## Key decisions - `authConfig.ts` stays as-is; `AuthProvider` accepts `UserManagerSettings` spread directly. - `setTokenProvider` stays in `api/client.ts` — `TokenSync` bridges between `react-oidc-context` and the fetch client without making the client depend on React. - Silent renew errors are handled automatically by `react-oidc-context`'s built-in event listeners — no custom wiring needed. ## Files affected - `package.json` (new dep: `react-oidc-context`) - `src/Budget.Client/src/main.tsx` - `src/Budget.Client/src/auth/AuthContext.tsx` (deleted) - `src/Budget.Client/src/auth/AuthGuard.tsx` - New: `src/Budget.Client/src/auth/TokenSync.tsx` - `src/Budget.Client/src/pages/CallbackPage.tsx`