fix(auth): prevent infinite session loop with non-reactive provider - #481
Conversation
Root cause: crossDomainClient plugin notifies $sessionSignal on EVERY response with set-better-auth-cookie header, causing useSession to refetch endlessly. Fix: - Add deduplicating storage that only writes when value actually changes - Replace ConvexBetterAuthProvider with custom StableAuthProvider - Use one-time session fetch instead of reactive useSession hook - Implement custom useStableConvexAuth for Convex token management
|
🚅 Deployed to the openchat-pr-481 environment in OpenChat
|
🚀 Preview Deployment Ready
Convex Preview Backend
🤖 Deployed automatically by GitHub Actions |
Greptile SummaryThis PR fixes a critical infinite loop bug causing ~4 req/sec to Key changes:
Issues found:
The approach is sound and addresses the root cause, but the two bugs in Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant Component
participant StableAuthProvider
participant AuthHook
participant AuthClient
participant Storage
participant ConvexProvider
participant Backend
Note over User,Backend: Initial Mount
User->>Component: Load App
Component->>StableAuthProvider: Mount
activate StableAuthProvider
StableAuthProvider->>AuthClient: getSession once
AuthClient->>Backend: Fetch session
Backend-->>AuthClient: Session data
AuthClient->>Storage: setItem check
Note over Storage: Value unchanged skip write
AuthClient-->>StableAuthProvider: Session result
StableAuthProvider->>StableAuthProvider: Update state
deactivate StableAuthProvider
Component->>AuthHook: Mount
activate AuthHook
AuthHook->>AuthClient: getSession once
AuthClient->>Backend: Fetch session
Backend-->>AuthClient: Session response
AuthClient->>Storage: setItem check
Note over Storage: Same value no write
AuthClient-->>AuthHook: Session data
AuthHook->>AuthClient: Get access details
AuthClient->>Backend: Fetch details
Backend-->>AuthClient: Details response
AuthClient-->>AuthHook: Details result
AuthHook->>AuthHook: Update state
deactivate AuthHook
Note over User,Backend: Convex Query
Component->>ConvexProvider: useQuery
ConvexProvider->>AuthHook: fetchAccessToken
AuthHook-->>ConvexProvider: Return cached value
ConvexProvider->>Backend: Convex query
Backend-->>ConvexProvider: Query result
ConvexProvider-->>Component: Data
Note over Component,Storage: Solution prevents infinite loop
|
| const [token, setToken] = useState<string | null>(null); | ||
| const [isLoading, setIsLoading] = useState(true); | ||
| const [isAuthenticated, setIsAuthenticated] = useState(false); | ||
| const fetchedRef = { current: false }; |
There was a problem hiding this comment.
logic: fetchedRef should be useRef(false) instead of a plain object
| const fetchedRef = { current: false }; | |
| const fetchedRef = useRef(false); |
| crossDomainClient({ | ||
| storage: deduplicatingStorage, | ||
| // Disable local session cache - we manage caching ourselves | ||
| disableCache: true, | ||
| }), |
There was a problem hiding this comment.
style: disableCache option may not exist on crossDomainClient - verify this in Better Auth docs to avoid runtime errors. Does the crossDomainClient plugin actually support a disableCache option?
| const fetchAccessToken = useCallback(async () => { | ||
| if (token) return token; | ||
| try { | ||
| const result = await authClient.convex.token(); | ||
| const newToken = result.data?.token || null; | ||
| setToken(newToken); | ||
| return newToken; | ||
| } catch { | ||
| return null; | ||
| } | ||
| }, [token]); |
There was a problem hiding this comment.
logic: early return with cached token doesn't refresh when token expires, causing potential auth failures
remove early return to always fetch fresh token
Summary
CRITICAL FIX: Stops the Better Auth session spam (~4 req/sec to
/api/auth/get-session) that was causing millions of Convex function calls and making the app unusable.Root Cause
The
crossDomainClientplugin from@convex-dev/better-authcallsstore.notify("$sessionSignal")on EVERY response withset-better-auth-cookieheader. This triggersuseSessionto refetch, creating an infinite loop:set-better-auth-cookieheader$sessionSignaluseSessionreacts and refetchesFix
$sessionSignalChanges
apps/web/src/lib/auth-client.tsx(renamed from .ts): Added deduplicating storage, StableAuthProvider, non-reactive useAuthapps/web/src/providers/index.tsx: Replaced ConvexBetterAuthProvider with StableAuthProvider + ConvexProviderWithAuthTesting