Search code examples
javascriptfirebasegoogle-cloud-firestorefirebase-authenticationfirebase-security

Firebase: authenticated and guest clients connecting to same Firestore Database


My use case is, I have a web app in which I listen to Firestore collections. One of the collections are for public users where anonymous auth is enough. And the second collection is for logged in users, so I implemented custom token auth following this. https://firebase.google.com/docs/auth/web/custom-auth. Once a user logs in, we start a Web Worker to initialise app and listen to Firestore.

My problem is, when a user logs in, the second subscription, the authenticated one, works, but if I try to go to public page, where I don't need the auth token, from the same tab, I get this error.

Uncaught (in promise) FirebaseError: Missing or insufficient permissions.
    at new Ur (prebuilt.js:184:9)
    at prebuilt.js:10612:34
    at br.<anonymous> (prebuilt.js:10564:21)
    at Bt (eventtarget.js:351:23)
    at qt (eventtarget.js:481:26)
    at Ir.wa (webchannelbasetransport.js:369:17)
    at Ke (webchannelbase.js:2258:25)
    at Fe (channelrequest.js:951:12)
    at Ne.N.Ia (channelrequest.js:743:10)
    at Ne.N.gb (channelrequest.js:603:10)

Both subscription uses the same firebase app, same configs.

More Info:

these are the rules

function isSignedIn(){
  return request.auth != null;
}

function isDocumentOwner(docid) {
  // checks if user id is the same as the document id
  return isSignedIn() && request.auth.uid == docid;
}
//this collection can be accessed for guest/anonymous users
match /guests/{uid} {
  allow read, write: if isDocumentOwner(uid);
}
//this collection in only accessible for logged in users(JWT token)
match /users/{uid} {
  allow read, write: if isDocumentOwner(uid);
}

And this is the init codes

Guest page:

const app = initializeApp(firebaseConfig);
db = getFirestore(app);
auth = getAuth();
signInAnonymously(auth);

onAuthStateChanged(auth, (loginUser) => {
  if (loginUser) {
    user = loginUser;
  } else {
    unsubscribe();
  }
});

Logged in page:

const app = initializeApp(firebaseConfig);
db = getFirestore(app);
auth = getAuth();
signInWithCustomToken(auth, customToken);

onAuthStateChanged(auth, (loginUser) => {
  if (loginUser) {
    user = loginUser;
  } else {
    unsubscribe();
  }
});

Solution

  • I figured it out. I connect to the same firebase app from two places. First one from a Web Worker, with custom token auth, second one from a different tab from the same browser with anonymous sign in. When the second connection try to initiate a connection with anonymous auth, it doesn't work because the app is already initiated with custom tokens, which fails this part of the rule,

    request.auth.uid == docid
    

    request.auth.uid is of the one who's logged in.

    Fix:

    I had to create another app and a set of configurations to be used for logged in users. and then, I had to give an app name when I initialising and authorising the app.

    const app = initializeApp(firebaseConfig, 'new-app');
    db = getFirestore(app);
    auth = getAuth(app);
    signInWithCustomToken(auth, customToken);
    
    onAuthStateChanged(auth, (loginUser) => {
      if (loginUser) {
        user = loginUser;
      } else {
        unsubscribe();
      }
    });