FirebaseExtended / FirebaseExtended/reactfire
Random permission errors when logging in with multiple tabs open
- Dominant language
- TypeScript
- Stars
- 3.6k
- Forks
- 403
- Avg merge
- 14h 53m
- Merged PRs (30d)
- 5
Description
### Version info
**React:** 18.2.0
**Firebase:** 9.15.0
**ReactFire:** 4.2.2
**Other (e.g. Node, browser, operating system) (if applicable):**
Node: 18.4.0
### Test case
When you have multi-tab persistence enabled (`enableMultiTabIndexedDbPersistence`) and you use the default auth persistence mode from `getAuth()` (browser local persistence), and your firebase app is open in multiple tabs, it occasionally fails to load the first requested document with a `code=permission-denied` error:
```
FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
```
Here's a short video demonstrating the problem:
https://user-images.githubusercontent.com/307312/210944853-38bfaf81-dc8e-4627-a29d-c2b31ac4f0a9.mp4
When the tab where the error occurred is reloaded the app loads just fine. The symptoms are very similar to those described in https://github.com/FirebaseExtended/reactfire/issues/485 and https://github.com/FirebaseExtended/reactfire/discussions/228, but the workaround of clearing the observables cache does not prevent this issue from occurring. The problem I'm having here sounds identical to https://github.com/firebase/firebase-js-sdk/issues/1981, but when I try loading the user document with the vanilla firebase SDK I actually don't get any permissions errors. It only happens when using the reactfire hooks.
Unfortunately, due to https://github.com/FirebaseExtended/reactfire/issues/540 it's impossible to catch the permissions error and deal with it, so I've had to resort to a really ugly hack where I inspect the preloaded observables cache, find the observable associated with my user document, and check if `ourError` contains a `permission-denied` error. If it does, I force the window to reload. It's not ideal because the error boundary still flashes for a second, and the error ends up in the console, but it's better than failing to load the app entirely. Here's what the workaround looks like:
```
// In some functional component:
applyReactfireWorkaround(`:users/${authUser.data.uid}`);
const user = useFirestoreDoc(...);
function applyReactfireWorkaround(...keySubstrings: string[]): void {
const reactFirePreloadedObservables = (globalThis as Record)['_reactFirePreloadedObservables'] as
| Map
| undefined;
if (reactFirePreloadedObservables) {
const hasPermissionDeniedError = Array.from(reactFirePreloadedObservables.keys())
.filter((key) => key.startsWith('firestore:'))
.filter((key) => keySubstrings.every((substring) => key.includes(substring)))
.map((key) => reactFirePreloadedObservables.get(key) as SuspenseSubject)
.some((subject) => (subject.ourError as FirebaseError | undefined)?.code === 'permission-denied');
if (hasPermissionDeniedError) {
globalThis.location.reload();
}
}
}
```
### Steps to reproduce
Log in and out of a Firebase app with multi-tab persistence enabled and browser local auth. A document subscription should be made immediately after logging in, e.g. to a document representing the current user.
### Expected behavior
All tabs load successfully.
### Actual behavior
One or more tabs blow up when accessing the first document with `useFirestoreDoc` with the following error:
```
FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
```
Contributor guide
Assessment
This issue has not been assessed yet.