Angularfire2 shows "Missing permissions" error, whilst straight firebase runs with no issue

Viewed 386

AngularFire2 seems to handle permissions differently to pure firebase sdk.

Steps to reproduce this issue:

  • Log in to a user account
  • Make a call to retrieve the firestore document for the user:
return this.afStore.doc('users/' + authId).valueChanges()
  .pipe(map(u => {
    if (u) {
      return new User(u);
    }
    return null;
  })
);
  • Log out of that user account and then to log in to another user account
  • Make the same call to retrieve the firestore document for the new user
  • This time is returns a Missing or insufficient permissions error

If I add the code for firebase directly into my index.html file, it works though:

var email = "xxx@xxx.com";
var password = "xxx";
var firebaseConfig = {...};

firebase.initializeApp(firebaseConfig);

firebase.auth().onAuthStateChanged((u) => {
  if (u) {
    firebase.firestore().doc('users/' + u.uid).get()
      .then(u => console.log("Got favorites", u.data()));
    firebase.firestore().doc('favorites/' + u.uid).get()
      .then(u => console.log("Got favorites", u.data()));
  }
});

firebase.auth().signInWithEmailAndPassword(email, password).then(cred => {
  console.log("Signed in", cred)
})

My firebase rules are as follows, but they seem to be ok in the simulator and obviously work from the front end some of the time:

service cloud.firestore {
  match /databases/{database}/documents {

    function internalGetUserFromReq() {
      return get(/databases/$(database)/documents/users/$(request.auth.uid))
    }

    //...

    match /users/{userID} {
      allow read, write: if internalGetUserFromReq().data.authId == userID;
      allow read, write: if internalGetUserFromReq().data.role == 0;
    }
}

FULL IMPLEMENTATION

For a more full picture, here is my login method:

this.afAuth.auth.signInWithEmailAndPassword(email, password).then((cred: firebase.auth.UserCredential) => {
  return this.user$;
}).catch((err: Error) => {
  this.logout();
  throw new Error(err.message);
});

I logout like this:

return this.afAuth.auth.signOut().then(() => {
  this.appState.clean();
  return this.router.navigate(['']);
});

And I am listening to the Firebase AuthState like this:

this.user$ = this.afAuth.authState.pipe(switchMap((user: firebase.User) => {
  if (user) {
    return this.getUser().pipe(catchError((err: Error) => {
      this.logout();
      return throwError(err);
    }));
  }
  return of(null);
})).pipe(share());

The getUser() method referenced in the switchMap is the original method I pasted in my question at the top, just returning an observable of the document that belongs to that user (e.g. users/12345).

RECAP

To recap the issue, the login/logout and AuthState observer all function as expected, but I when add firebase rules to my Firestore documents that would restrict the user to only access their own document (i.e. where authID matches request.auth.uid), then the document becomes inaccessible. This behaviour only occurs through the AngularFire2 package, and persists after the page is reloaded and even when opening the app in a brand new browser with clean cookies and cache.

WORK AROUND

For anyone else experiencing this issue, I have had to strip out AngularFire2 (and same applies to the new @angular/fire package) and replace it with my own wrapper around the core firebase package.

REPRODUCTION

I am unable to create a reproduction at the moment - I know that's annoying and I will try to do one when I have more time. However, the issue is so deep rooted in AngularFire2 that I will have to create a fresh angular app, setup a new Firebase instance, complete with authentication, a Firestore database, and document access rules and then also implement a full user login journey. To do the issue justice, I would also need to setup the same again but without AngularFire2, just to prove that it is that package causing the issue. It's too much work for me to cram into my rare days off at the moment!

If anyone has time to tinker and wants to set one up, drop me a message and I will gladly help.

1 Answers

when you log in you get the firebase auth user,you take its Id and then you subscribe to the user document from the database, probably you are using 2 subscription, one to the auth and the second to the user full document (we don't see the full authentication code here), now when you are logout out your user document subscription is still running but you don't have the premission to get the user full document now because you not loged in anymore (and the rules are about the auth user), my solution is to use switchMap with angularFire auth so there are only one subscription (this is actually kind of what you did in the later code - in the html only)

** your error is probably because of the logout and not the new login

private _sub: Subscription;

constructor(private _auth: AngularFireAuth, private _db: AngularFirestore){
     this._sub = this._auth.authState
        .pipe(
            switchMap(u => (u)
                ? this._db.collection('Users).doc(u.uid)
                    .valueChanges()
                : of(null))
        .subscribe(user => {
           // some actions
            });
}

Now when you logout firebase will not try to get the user document again and get this error

Related