Operating System
macOS 15, Windows 11 (any; the behavior is in the SDK's auth initialization)
Environment (if applicable)
Chrome 129, Electron 31, Web Worker (all three surfaces of our app); reproduced with the browser SDK
Firebase SDK Version
10.12.3 (@firebase/auth 1.7.5). Verified unchanged on main as of 2026-09-23; latest release 12.19.0 (@firebase/auth 1.13.6) has the same reloadAndSetCurrentUserOrClear.
Firebase SDK Product(s)
Auth
Project Tooling
ClojureScript (shadow-cljs) SPA using the modular firebase JS SDK; also loaded via the compat layer in an Electron desktop wrapper
Detailed Problem Description
What we were trying to achieve
Keep users signed in through a temporary Identity Toolkit quota exhaustion.
What actually happened
When the project's Identity Toolkit quota is exhausted (a credential-stuffing attack on our
public web API key did this for hours at a time on 2026-09-16 through 09-19), every user who
loaded the app was signed out, and stayed signed out after the quota recovered. On load,
initializeCurrentUser calls reloadAndSetCurrentUserOrClear, which calls _reloadWithoutSaving,
which calls accounts:lookup. Google answers HTTP 400 with
{"error":{"code":400,"message":"QUOTA_EXCEEDED"}}. reloadAndSetCurrentUserOrClear treats every
error except auth/network-request-failed as "something's wrong with the user's token" and calls
directlySetCurrentUser(null), which removes the persisted user from storage. The refresh token
was valid the whole time; nothing about the session had changed.
Why this is a bug rather than a policy choice
QUOTA_EXCEEDED is a statement about the project's rate limit, not about the user. It is
transient by definition. The SDK already recognizes that a network failure says nothing about
the token and keeps the user in that case (the comment in api/index.ts says so explicitly).
A quota rejection is the same kind of event: the request was not evaluated. Treating it as
token invalidation converts a partial outage (new sign-ins fail until the quota window clears)
into a total one (everyone is signed out and cannot sign back in while the quota is exhausted),
and the sign-out persists after recovery because storage was cleared.
Google Cloud Support has confirmed the mechanism in our support case and pointed us here:
"Client SDKs interpret this response as an unrecoverable failure during token refreshes, which
clears the user's local session," and "a quota exhaustion is a transient infrastructure event,
not an authentication revocation, and it ideally should not trigger a permanent session wipe on
the client side."
The same applies to the HTTP 429 the Google API frontend returns when the console quota
"Queries per minute per user" is exceeded (a JSON body with a RESOURCE_EXHAUSTED message).
Whatever AuthErrorCode these map to, neither is auth/network-request-failed, so both clear
the user.
Proposed fix
In reloadAndSetCurrentUserOrClear, keep the persisted user for errors that do not indicate an
invalid token: auth/quota-exceeded, auth/too-many-requests, HTTP 429, and 5xx responses, in
addition to auth/network-request-failed. The user object stays as it was (possibly stale until
the next successful reload), which is exactly what happens today for a network failure.
Steps and code to reproduce issue
- In a test Firebase project, set the Identity Toolkit quota "Queries per minute per user" to
its minimum (e.g. 20) in the Cloud Console (APIs & Services > Identity Toolkit API > Quotas).
Wait about 10 minutes for it to propagate.
- Sign in with email/password in a page using the browser SDK with default (IndexedDB)
persistence. Confirm auth.currentUser is set and the firebaseLocalStorageDb entry exists.
- From the same IP, exhaust the quota: about 25 requests to
https://identitytoolkit.googleapis.com/v1/accounts:lookup?key= in one minute
(any body, e.g. {"idToken":"x"}). Responses switch to 429 after roughly 20.
- Reload the page within the same minute.
Expected: the user is still signed in (as they would be if the network were down), and the
next reload after the quota clears succeeds.
Observed: onAuthStateChanged fires with null, the persisted user is gone from
firebaseLocalStorageDb, and the user must sign in again once the quota clears.
Code path (main):
- packages/auth/src/core/auth/auth_impl.ts, initializeCurrentUser ->
reloadAndSetCurrentUserOrClear: catches the reload error, and if e.code !==
'auth/network-request-failed' calls this.directlySetCurrentUser(null).
- packages/auth/src/core/user/reload.ts, _reloadWithoutSaving -> getAccountInfo
(accounts:lookup).
- packages/auth/src/api/index.ts, comment above the NETWORK_REQUEST_FAILED fallback: "we treat
any error other than NETWORK_REQUEST_FAILED as token is invalid."
The production incident used the HTTP 400 QUOTA_EXCEEDED form (project-wide "Queries per
minute" exhausted); the per-user quota above is the cheapest way to reproduce the same
clearing behavior in a test project.
Operating System
macOS 15, Windows 11 (any; the behavior is in the SDK's auth initialization)
Environment (if applicable)
Chrome 129, Electron 31, Web Worker (all three surfaces of our app); reproduced with the browser SDK
Firebase SDK Version
10.12.3 (@firebase/auth 1.7.5). Verified unchanged on main as of 2026-09-23; latest release 12.19.0 (@firebase/auth 1.13.6) has the same reloadAndSetCurrentUserOrClear.
Firebase SDK Product(s)
Auth
Project Tooling
ClojureScript (shadow-cljs) SPA using the modular firebase JS SDK; also loaded via the compat layer in an Electron desktop wrapper
Detailed Problem Description
What we were trying to achieve
Keep users signed in through a temporary Identity Toolkit quota exhaustion.
What actually happened
When the project's Identity Toolkit quota is exhausted (a credential-stuffing attack on our
public web API key did this for hours at a time on 2026-09-16 through 09-19), every user who
loaded the app was signed out, and stayed signed out after the quota recovered. On load,
initializeCurrentUser calls reloadAndSetCurrentUserOrClear, which calls _reloadWithoutSaving,
which calls accounts:lookup. Google answers HTTP 400 with
{"error":{"code":400,"message":"QUOTA_EXCEEDED"}}. reloadAndSetCurrentUserOrClear treats every
error except auth/network-request-failed as "something's wrong with the user's token" and calls
directlySetCurrentUser(null), which removes the persisted user from storage. The refresh token
was valid the whole time; nothing about the session had changed.
Why this is a bug rather than a policy choice
QUOTA_EXCEEDED is a statement about the project's rate limit, not about the user. It is
transient by definition. The SDK already recognizes that a network failure says nothing about
the token and keeps the user in that case (the comment in api/index.ts says so explicitly).
A quota rejection is the same kind of event: the request was not evaluated. Treating it as
token invalidation converts a partial outage (new sign-ins fail until the quota window clears)
into a total one (everyone is signed out and cannot sign back in while the quota is exhausted),
and the sign-out persists after recovery because storage was cleared.
Google Cloud Support has confirmed the mechanism in our support case and pointed us here:
"Client SDKs interpret this response as an unrecoverable failure during token refreshes, which
clears the user's local session," and "a quota exhaustion is a transient infrastructure event,
not an authentication revocation, and it ideally should not trigger a permanent session wipe on
the client side."
The same applies to the HTTP 429 the Google API frontend returns when the console quota
"Queries per minute per user" is exceeded (a JSON body with a RESOURCE_EXHAUSTED message).
Whatever AuthErrorCode these map to, neither is auth/network-request-failed, so both clear
the user.
Proposed fix
In reloadAndSetCurrentUserOrClear, keep the persisted user for errors that do not indicate an
invalid token: auth/quota-exceeded, auth/too-many-requests, HTTP 429, and 5xx responses, in
addition to auth/network-request-failed. The user object stays as it was (possibly stale until
the next successful reload), which is exactly what happens today for a network failure.
Steps and code to reproduce issue
its minimum (e.g. 20) in the Cloud Console (APIs & Services > Identity Toolkit API > Quotas).
Wait about 10 minutes for it to propagate.
persistence. Confirm auth.currentUser is set and the firebaseLocalStorageDb entry exists.
https://identitytoolkit.googleapis.com/v1/accounts:lookup?key= in one minute
(any body, e.g. {"idToken":"x"}). Responses switch to 429 after roughly 20.
Expected: the user is still signed in (as they would be if the network were down), and the
next reload after the quota clears succeeds.
Observed: onAuthStateChanged fires with null, the persisted user is gone from
firebaseLocalStorageDb, and the user must sign in again once the quota clears.
Code path (main):
reloadAndSetCurrentUserOrClear: catches the reload error, and if e.code !==
'auth/network-request-failed' calls this.directlySetCurrentUser(null).
(accounts:lookup).
any error other than NETWORK_REQUEST_FAILED as token is invalid."
The production incident used the HTTP 400 QUOTA_EXCEEDED form (project-wide "Queries per
minute" exhausted); the per-user quota above is the cheapest way to reproduce the same
clearing behavior in a test project.