Skip to content

fix(firebase-frameworks): don't delete an in-flight app on concurrent - #689

Merged
leoortizz merged 3 commits into
mainfrom
fix/frameworks-auth-lru-race
Sep 23, 2026
Merged

leoortizz merged 3 commits into
mainfrom
fix/frameworks-auth-lru-race

Conversation

@leoortizz

Copy link
Copy Markdown
Member

Fixes #685

Problem

firebaseAppsLRU is keyed by uid and disposes with deleteApp. Two separate paths hand a request an app that is already being deleted.

Concurrent requests with the same __session cookie all miss the cache, so each one calls initializeApp and each set() deletes the app the previous request is still signing in with. The losers throw app/app-deleted, handleFactory does not catch, and Express answers 500.

A single request does it too, once the entry goes stale. A stale get() deletes the entry, which fires dispose and deleteApp, then returns that same app. deleteApp is async and nothing awaits it, and the rest of handleAuth is synchronous, so the request never notices. Teardown finishes while the page is awaiting, and the next use of res.locals.firebaseApp throws.

Fix

  • Re-check the cache after the awaited revocation check, so only the first request initializes an app.
  • Drop allowStale.
  • Catch in handleFactory, so an auth failure degrades to an unauthenticated request instead of a 500.

Verification

Next.js 14.2 with the Firebase JS SDK, deployed to Hosting preview channels. Statuses come from the SSR function's own request log.

App Router, the reported scenario: eight <Link>s mount right after sign-in and prefetch at once against a cold entry.

firebase-frameworks at the function
0.11.8 seven 500s with app/app-deleted, one 200
this branch eight 200s

The 0.11.8 latencies match the table in the issue.

The stale path was checked on its own, with a page that awaits getIdToken() and then reads app.options. Once the entry is left idle past its 5 minute LRU_TTL, 0.11.8 serves the stale entry and the page fails with app/app-deleted, while this branch re-initializes and succeeds.

same-uid requests

Concurrent requests for one uid each created their own app, and every
set()
disposed the previous one while it was still signing in. The losers
threw
app/app-deleted and the request ended in a 500. Now:
- the cache is re-checked after the await so only the first request
  creates the app
- allowStale is gone because a stale get() handed back an already
  deleted app
- an auth failure falls through as an unauthenticated request.

Fixes #685

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request removes the allowStale option from the LRU cache, handles concurrent app initialization in handleAuth by checking the cache again after awaiting session cookie verification, and catches authentication errors in handleFactory to treat them as unauthenticated requests. The review feedback highlights a potential race condition and performance bottleneck with concurrent requests executing signInWithCustomToken simultaneously, suggesting caching the sign-in promise on the app instance to prevent redundant operations.

Comment thread packages/firebase-frameworks/src/firebase-aware.ts

@Yuangwang Yuangwang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@leoortizz
leoortizz merged commit 333b931 into main Sep 23, 2026
21 checks passed
@leoortizz
leoortizz deleted the fix/frameworks-auth-lru-race branch September 23, 2026 23:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

firebase-frameworks: handleAuth 500s under concurrent same-uid requests (uid-keyed LRU dispose deletes an in-flight Firebase App)

2 participants