firebase-js-sdk icon indicating copy to clipboard operation
firebase-js-sdk copied to clipboard

Firestore not working in beforeAuthStateChanged

Open polRk opened this issue 1 year ago • 3 comments

Operating System

macOS

Browser Version

125.0.6422.141

Firebase SDK Version

10.12.2

Firebase SDK Product:

Auth, Firestore

Describe your project's tooling

Vite

Describe the problem

Missing Authorization header when requesting firestore inside beforeAuthStateChanged.

NOT WORKING

beforeAuthStateChanged(auth, async (user) => {
	if (!user) {
		return
	}

	await auth.authStateReady()

	await runTransaction(firestore, async (transaction) => {
		const docRef = doc(firestore, 'users', user.uid)
		const docSnap = await transaction.get(docRef)
		if (!docSnap.exists()) {
			transaction.set(docRef, { email: user.email })
		}

		return
	})
})

WORKING

onAuthStateChanged(auth, async (user) => {
	if (!user) {
		return
	}

	await auth.authStateReady()

	await runTransaction(firestore, async (transaction) => {
		const docRef = doc(firestore, 'users', user.uid)
		const docSnap = await transaction.get(docRef)
		if (!docSnap.exists()) {
			transaction.set(docRef, { email: user.email })
		}

		return
	})
})

Steps and code to reproduce issue

// rules
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow create, read, update: if request.auth != null && request.auth.uid == userId;
    }

    match /{document=**} {
      allow read, write: if false;
    }
  }
}

const auth = getAuth(app)
const firestore = getFirestore(app)

beforeAuthStateChanged(auth, async (user) => {
	if (!user) {
		return
	}

	await auth.authStateReady()

	await runTransaction(firestore, async (transaction) => {
		const docRef = doc(firestore, 'users', user.uid)
		const docSnap = await transaction.get(docRef)
		if (!docSnap.exists()) {
			transaction.set(docRef, { email: user.email })
		}

		return
	})
})

polRk avatar Jun 06 '24 05:06 polRk

Can you provide more details about your use case and why you need to run this code inside beforeAuthStateChanged instead of in onAuthStateChanged?

I also wouldn't recommend awaiting onAuthStateReady() inside either of those functions. It checks if currentUser exists, and resolves immediately if so, and otherwise calls onAuthStateChanged() and resolves when its callback resolves. This is redundant inside onAuthStateChanged() as it's simply waiting for its own trigger, which has already happened. Inside beforeAuthStateChanged() it also doesn't make sense.

The immediate problem here is that beforeAuthStateChanged() occurs before the user has been written to persistence/memory and also before triggering any listeners waiting to see if auth state has changed, listeners which include not only onAuthStateChanged() but also Firestore's listeners waiting for an auth token. Its main use case is for SSR apps where backend auth state needs to be kept in sync with frontend auth state, and similar cases. At this point, Firestore's listeners have not yet been notified that an auth token is available.

I can't think of a use case where you would need to set an email field that early that couldn't wait until after the user state has been written to persistence/memory. I guess I could see that you might want to check it against allowlisted/blocklisted emails stored on Firestore or elsewhere, and block users from completing login if they're on that list. In any case, whatever your data source is, this all happens before login completes, so it's technically available to anyone and shouldn't be anything security-gated.

Side note: I noticed that beforeAuthStateChanged() does not always run before onAuthStateChanged() - namely it doesn't run before the initial onAuthStateChanged() on page load. It does run on every subsequent login/logout. Should look into if this is intended.

hsubox76 avatar Jun 07 '24 19:06 hsubox76

Hey @polRk. We need more information to resolve this issue but there hasn't been an update in 5 weekdays. I'm marking the issue as stale and if there are no new updates in the next 5 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

google-oss-bot avatar Jun 21 '24 01:06 google-oss-bot

@hsubox76 since beforeAuthStateChanged is a blocking operation—intended for middleware, syncing cookies, etc.—i think it not running unless the token has actually changed (even on page load) is WAI. Ultimately to me it boils down to if you threw in beforeAuthStateChanged on page load you wouldn't be blocking the authStateChanged fire or the passing of the token to the SDKs, since the token is already persisted. In hindsight, we probably should have named it beforeIdTokenChanged

Agree that this issue is not an appropriate use of the API, as Firestore has no auth token to key off of since this is hook is "before" the token is persisted / made available to SDKs

jamesdaniels avatar Jun 27 '24 16:06 jamesdaniels

Since there haven't been any recent updates here, I am going to close this issue.

@polRk if you're still experiencing this problem and want to continue the discussion just leave a comment here and we are happy to re-open this.

google-oss-bot avatar Jul 09 '24 01:07 google-oss-bot