Replace custom AuthContext with react-oidc-context
Deletes the hand-rolled AuthContext/UserManager setup and replaces it with AuthProvider from react-oidc-context. onSigninCallback clears the OIDC code params from the URL (unless an error is present). TokenSync bridges the library token into the existing api/client setTokenProvider pattern. AuthGuard updated to use auth.isLoading/isAuthenticated/ signinRedirect from the library. CallbackPage simplified to a passive error renderer — react-oidc-context processes the OIDC exchange itself. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { useEffect } from 'react';
|
||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { AuthProvider, useAuth } from './auth/AuthContext';
|
||||
import { AuthProvider } from 'react-oidc-context';
|
||||
import { authConfig } from './auth/authConfig';
|
||||
import { TokenSync } from './auth/TokenSync';
|
||||
import { AuthGuard } from './auth/AuthGuard';
|
||||
import { ErrorBoundary } from './components/ErrorBoundary';
|
||||
import { ToastProvider } from './components/Toast';
|
||||
import { setTokenProvider } from './api/client';
|
||||
import { CallbackPage } from './pages/CallbackPage';
|
||||
import { BudgetsPage } from './pages/BudgetsPage';
|
||||
import { IncomePage } from './pages/IncomePage';
|
||||
@@ -12,18 +12,18 @@ import { OutgoPage } from './pages/OutgoPage';
|
||||
import { SummaryPage } from './pages/SummaryPage';
|
||||
import { SettingsPage } from './pages/SettingsPage';
|
||||
|
||||
function TokenWirer() {
|
||||
const { getToken } = useAuth();
|
||||
useEffect(() => { setTokenProvider(getToken); }, [getToken]);
|
||||
return null;
|
||||
}
|
||||
const onSigninCallback = () => {
|
||||
if (!window.location.search.includes('error')) {
|
||||
window.history.replaceState({}, '', '/');
|
||||
}
|
||||
};
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<ToastProvider>
|
||||
<AuthProvider>
|
||||
<TokenWirer />
|
||||
<AuthProvider {...authConfig} onSigninCallback={onSigninCallback}>
|
||||
<TokenSync />
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/budgets" replace />} />
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { UserManager } from 'oidc-client-ts';
|
||||
import type { User } from 'oidc-client-ts';
|
||||
import { authConfig } from './authConfig';
|
||||
|
||||
interface AuthContextValue {
|
||||
user: User | null;
|
||||
isLoading: boolean;
|
||||
login: () => void;
|
||||
logout: () => void;
|
||||
getToken: () => string | null;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextValue | null>(null);
|
||||
|
||||
const userManager = new UserManager(authConfig);
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
userManager.getUser().then(u => {
|
||||
setUser(u);
|
||||
setIsLoading(false);
|
||||
});
|
||||
|
||||
const onUserLoaded = (u: User) => setUser(u);
|
||||
const onUserUnloaded = () => setUser(null);
|
||||
|
||||
userManager.events.addUserLoaded(onUserLoaded);
|
||||
userManager.events.addUserUnloaded(onUserUnloaded);
|
||||
|
||||
return () => {
|
||||
userManager.events.removeUserLoaded(onUserLoaded);
|
||||
userManager.events.removeUserUnloaded(onUserUnloaded);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const login = () => userManager.signinRedirect();
|
||||
const logout = () => userManager.signoutRedirect();
|
||||
const getToken = () => user?.access_token ?? null;
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, isLoading, login, logout, getToken }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
const ctx = useContext(AuthContext);
|
||||
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export { userManager };
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { useAuth } from './AuthContext';
|
||||
import { useAuth } from 'react-oidc-context';
|
||||
|
||||
export function AuthGuard({ children }: { children: ReactNode }) {
|
||||
const { user, isLoading, login } = useAuth();
|
||||
const auth = useAuth();
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (auth.isLoading) return <div>Loading...</div>;
|
||||
|
||||
if (!user) {
|
||||
login();
|
||||
if (!auth.isAuthenticated) {
|
||||
auth.signinRedirect();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useAuth } from 'react-oidc-context';
|
||||
import { setTokenProvider } from '../api/client';
|
||||
|
||||
export function TokenSync() {
|
||||
const auth = useAuth();
|
||||
useEffect(() => {
|
||||
setTokenProvider(() => auth.user?.access_token ?? null);
|
||||
}, [auth.user]);
|
||||
return null;
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { userManager } from '../auth/AuthContext';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
export function CallbackPage() {
|
||||
const navigate = useNavigate();
|
||||
const [params] = useSearchParams();
|
||||
const error = params.get('error');
|
||||
const errorDescription = params.get('error_description');
|
||||
|
||||
useEffect(() => {
|
||||
userManager.signinRedirectCallback()
|
||||
.then(() => navigate('/budgets'))
|
||||
.catch(err => {
|
||||
console.error('OIDC callback error', err);
|
||||
navigate('/');
|
||||
});
|
||||
}, [navigate]);
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<h2>Sign-in failed</h2>
|
||||
<p>{errorDescription ?? error}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div>Signing in...</div>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user