refactor: replace hand-rolled OIDC with @logto/react SDK
The hand-rolled OIDC flow (manual PKCE, token exchange, URL construction) was fragile and accumulated multiple bugs. Replaced with the official @logto/react SDK which handles PKCE, token exchange, storage, and refresh automatically. - Add @logto/react SDK dependency - Add LogtoProvider with runtime config in main.tsx - Add TokenSync component bridging SDK tokens to API client - Add useAuth hook replacing Zustand auth store - Simplify LoginPage to signIn(), CallbackPage to useHandleSignInCallback() - Delete pkce.ts and auth-store.ts (replaced by SDK) - Fix react-router-dom → react-router imports in page files - All 17 React Query hooks unchanged (token provider pattern) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { LogtoProvider, useLogto } from '@logto/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { BrowserRouter } from 'react-router';
|
||||
import { ThemeProvider, ToastProvider, BreadcrumbProvider } from '@cameleer/design-system';
|
||||
import { ThemeProvider, ToastProvider, BreadcrumbProvider, Spinner } from '@cameleer/design-system';
|
||||
import '@cameleer/design-system/style.css';
|
||||
import { AppRouter } from './router';
|
||||
import { fetchConfig } from './config';
|
||||
import { setTokenProvider, setLogoutHandler } from './api/client';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@@ -15,16 +18,73 @@ const queryClient = new QueryClient({
|
||||
},
|
||||
});
|
||||
|
||||
function TokenSync({ resource }: { resource: string }) {
|
||||
const { getAccessToken, isAuthenticated, signOut } = useLogto();
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthenticated && resource) {
|
||||
setTokenProvider(() => getAccessToken(resource));
|
||||
} else {
|
||||
setTokenProvider(null);
|
||||
}
|
||||
}, [isAuthenticated, getAccessToken, resource]);
|
||||
|
||||
const handleLogout = useCallback(() => {
|
||||
signOut(window.location.origin + '/login');
|
||||
}, [signOut]);
|
||||
|
||||
useEffect(() => {
|
||||
setLogoutHandler(handleLogout);
|
||||
return () => setLogoutHandler(null);
|
||||
}, [handleLogout]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [config, setConfig] = useState<{
|
||||
logtoEndpoint: string;
|
||||
logtoClientId: string;
|
||||
logtoResource: string;
|
||||
} | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchConfig().then(setConfig);
|
||||
}, []);
|
||||
|
||||
if (!config?.logtoClientId) {
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh' }}>
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<LogtoProvider
|
||||
config={{
|
||||
endpoint: config.logtoEndpoint,
|
||||
appId: config.logtoClientId,
|
||||
resources: config.logtoResource ? [config.logtoResource] : [],
|
||||
scopes: ['openid', 'profile', 'email', 'offline_access'],
|
||||
}}
|
||||
>
|
||||
<TokenSync resource={config.logtoResource} />
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<AppRouter />
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
</LogtoProvider>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<ThemeProvider>
|
||||
<ToastProvider>
|
||||
<BreadcrumbProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<AppRouter />
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
<App />
|
||||
</BreadcrumbProvider>
|
||||
</ToastProvider>
|
||||
</ThemeProvider>
|
||||
|
||||
Reference in New Issue
Block a user