Search code examples
javascriptgoogle-cloud-firestorefirebase-authenticationfirebase-securityangularfire2

Firestore check if the logged in user verified his email


So I have a collection in firestore that should only be accessible to logged in and verified users.

This is how I secure it:

rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {
    function emailVerified() {
      return request.auth.token.firebase.sign_in_provider != 'password'
            || request.auth.token.email_verified == true;
    }

    match /users/{userId} {
      allow get: if request.auth.uid != null && emailVerified();
    }
  }
}

Using the rules emulator this works as expected:

Auth Provider Email not verified Email verified
Google ✔️ ✔️ (but not really needed, since we don't send verifications for providers other than email)
Email ✔️

But using the frontend this happens:

Auth Provider Email not verified Email verified
Google ✔️ ✔️
Email ❌ → fails with a Missing or insufficient permissions

Here are the relevant snippets from my frontend code:

// currentAuthUserObs() is the current auth user from firebase  (that part works)
// isEmailVerified() is described further down (works too)
// afStorage is AngularFire2's Firestore module

// 'HERE' and 'HERE2' are both printed, so that works as expected. The problem really is the query

this.auth.currentAuthUserObs().subscribe(async (authUser) => {
    console.log('HERE');
    if (authUser != null && await this.auth.emailAuth.isEmailVerified()) {
        console.log('HERE2');
        this.afStorage.doc(`${FirestoreCollections.USERS}/${authUser.uid}`).valueChanges()
            .subscribe(user => {
                console.log(user);
            });
    }
});


// In my case isEmailVerified gets called before and refreshes the currentUser, so it is ok to not call it with `refresh = true` in the subscription above.
public async isEmailVerified(refresh = false): Promise<boolean> {
    if (refresh) {
        await firebase.auth().currentUser.reload();
    }

    const isEmail = () => firebase.auth().currentUser.providerData.some(provider => provider.providerId == 'password');
    return isEmail() ? firebase.auth().currentUser.emailVerified : true;
}

Does someone have any clue what the problem could possibly be?


Solution

  • As the user @Kato pointed out, the ID token of the user needs a refresh. You can do that using firebase.auth().currentUser.getIdToken(true) - which looks like that in my example:

    public async isEmailVerified(refresh = false): Promise<boolean> {
        if (refresh) {
            await firebase.auth().currentUser.reload();
            await firebase.auth().currentUser.getIdToken(true); // <-- here
        }
    
        const isEmail = () => firebase.auth().currentUser.providerData.some(provider => provider.providerId == 'password');
        return isEmail() ? firebase.auth().currentUser.emailVerified : true;
    }