Files
budget/.plans/11-react-oidc-context.md
T
Spencer Twaddle 087fbdd176 Split into Budget.Core / Budget.Infrastructure / Budget.Api projects
Budget.Core: entities, DTOs, enums, FrequencyCalculator (no EF/ASP.NET deps)
Budget.Infrastructure: AppDbContext, migrations, BudgetAuthorizationService
Budget.Api: controllers, middleware, Program.cs — references both projects

EF and Npgsql packages moved to Infrastructure; Api retains only JwtBearer,
HealthChecks, and EF.Design (needed for dotnet ef CLI). Dockerfile updated
to copy all three project directories before publishing. Migration namespaces
updated from Budget.Api.Data.* to Budget.Infrastructure.Data.* and model
type strings updated to Budget.Core.Models.* in the snapshot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 16:30:31 -05:00

4.0 KiB

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.tsUserManagerSettings 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.tssetTokenProvider(fn) wired in main.tsx
  • src/Budget.Client/src/main.tsx — wraps app in <AuthProvider>

Target state

  • Remove AuthContext.tsx.
  • main.tsx wraps the app in <AuthProvider> 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

  1. Delete src/Budget.Client/src/auth/AuthContext.tsx.
  2. 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: <AuthProvider {...authConfig} onSigninCallback={...}>.
    • Remove the old <AuthProvider> import and setTokenProvider wiring.
  3. Create src/Budget.Client/src/auth/TokenSync.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;
    }
    
  4. Render <TokenSync /> inside <AuthProvider> in main.tsx.

Phase 3 — Update AuthGuard and CallbackPage

  1. 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
  2. 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

  1. Remove export { userManager } from the old AuthContext (now deleted) and fix any imports that referenced it (only CallbackPage should have used it).
  2. 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.tsTokenSync 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