fix: prevent URL sync effects from stripping Auth0 callback params (refs #188)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m21s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 51s
Deploy to Staging / Verify Staging (pull_request) Successful in 9s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped

Root cause: React fires child effects before parent effects. App's URL
sync effect called history.replaceState() on /callback, stripping the
?code= and &state= query params before Auth0Provider's useEffect could
read them via hasAuthParams(). The SDK fell through to checkSession()
instead of handleRedirectCallback(), silently failing with no error.

Guard both URL sync effects to skip on /callback, /signup, /verify-email.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-02-15 09:24:56 -06:00
parent b5b82db532
commit 850f713310

View File

@@ -365,17 +365,24 @@ function App() {
const [showAddVehicle, setShowAddVehicle] = useState(false); const [showAddVehicle, setShowAddVehicle] = useState(false);
// Sync browser URL to Zustand screen state on mount (enables direct URL navigation on mobile) // Sync browser URL to Zustand screen state on mount (enables direct URL navigation on mobile)
// Skip on auth routes -- their query params must survive until Auth0 SDK processes them
useEffect(() => { useEffect(() => {
const screen = routeToScreen[window.location.pathname]; const path = window.location.pathname;
if (path === '/callback' || path === '/signup' || path === '/verify-email') return;
const screen = routeToScreen[path];
if (screen && screen !== activeScreen) { if (screen && screen !== activeScreen) {
navigateToScreen(screen, { source: 'url-sync' }); navigateToScreen(screen, { source: 'url-sync' });
} }
}, []); // eslint-disable-line react-hooks/exhaustive-deps -- intentionally runs once on mount }, []); // eslint-disable-line react-hooks/exhaustive-deps -- intentionally runs once on mount
// Sync Zustand screen changes back to browser URL (enables bookmarks and URL sharing) // Sync Zustand screen changes back to browser URL (enables bookmarks and URL sharing)
// Skip on auth routes -- replaceState would strip ?code= and &state= params that
// Auth0 SDK needs for handleRedirectCallback (child effects fire before parent effects)
useEffect(() => { useEffect(() => {
const path = window.location.pathname;
if (path === '/callback' || path === '/signup' || path === '/verify-email') return;
const targetPath = screenToRoute[activeScreen]; const targetPath = screenToRoute[activeScreen];
if (targetPath && window.location.pathname !== targetPath) { if (targetPath && path !== targetPath) {
window.history.replaceState(null, '', targetPath); window.history.replaceState(null, '', targetPath);
} }
}, [activeScreen]); }, [activeScreen]);