Angularfire: Firestore emulator calls Google APIs to check auth token

Created on 15 Nov 2020  路  9Comments  路  Source: angular/angularfire

This issue is kinda related to #2637 since it deals with the same end result.
I figured this bug deserves its own issue though, because it seems so be related to Firestore this time.

Version info

Angular:
10.2.0

Firebase:
8.0.1

AngularFire:
6.1.0-rc.3

How to reproduce these conditions

Failing test unit, Stackblitz demonstrating the problem

https://github.com/jornetsimon/af-auth-debug
Updated with commit reproducing the issue.

Steps to set up and reproduce

  • Set up the Authentication & Firestore emulators using the new 6.1 injection tokens.
  • Set up an authentication method (email/password for example) and implement it on a page
  • Subscribe to authState
  • Inject AngularFireStore on a separate routed component (lets call it ComponentB)
  • Sign up : no issue
  • Reload the app, navigate to ComponentB :
  • Now load the app directly on the route pointing to ComponentB (e.g. /componentb): a call to Firebase servers (googleapis.com) is made, returning a 400 INVALID_ID_TOKEN error. The user is therefore logged out.

Sample data and security rules

Providers config:`

providers: [
  {
    provide: USE_FIRESTORE_EMULATOR,
    useValue: environment.useEmulators ? ['localhost', 8888] : undefined,
  },
  {
    provide: USE_AUTH_EMULATOR,
    useValue: environment.useEmulators ? ['localhost', 9099] : undefined,
  },
],

Debug output

When using emulators :
98449248-42ef6a00-2132-11eb-94b1-e101862f56a8

Expected behavior

The AngularFirestore service should target the emulator to verify the auth token.

Actual behavior

There is no problem when authenticating THEN navigating to a component using the AngularFirestore service.

But when loading the app directly through the firestore component endpoint, it seems like it doesn't have the time to be aware of the emulator config...

Note : The issue is the same with 6.1.0-rc.2 and 6.1.0-rc.3.

Most helpful comment

I've done the best I could with the current API, there's now a warning in the soon to be cut 6.1.0.rc-4

All 9 comments

This is interesting, it appears to be trigged by the fact that AngularFireAuth dynamically imports firebase/auth... if you add import 'firebase/auth'; directly in the injects-firestore component it functions correctly.

I wonder if when useEmulator is called in Firestore, before auth is loaded (because it's dynamic), and then Firestore assumes production auth... which is weird because you called useEmulator.

I don't actually see an easy way around this with the current API... If AngularFirestore was a lazy loading module I would pull in AngularFireAuth as an optional and ensure it was initialized first. Maybe AngularFirestore can inject the USE_EMULATOR token from auth and give a warning at the very least.

I've done the best I could with the current API, there's now a warning in the soon to be cut 6.1.0.rc-4

I am trying to work this around by importing "firebase/auth" from the very root of the application, but the issue remains. Any idea?

if it can help anyone, for the time being, my workaround is this:

// add provider in you AppModule
{
    provide: APP_INITIALIZER,
    multi: true,
    useFactory: useEmulatorProvider,
  }

function useEmulatorProvider() {
  return () => {
    if (isEmulator) {
      const firebase = require('firebase/app').default;
      firebase.auth().useEmulator(`http://localhost:9099`);
    }
  };
}

The trouble is that no matter how you slice it, unless you initialize auth and firestore/database at the same time inline in your APP_INITIALIZER, the behavior is indeterminate. Does auth load first or database/firestore?... no way to control with the exiting API.

I will be prioritizing the addition of a lazy version of Database/Firestore/Storage in the next AngularFire minor, I've been putting that off for way too long. Not only would a lazy versions reduce your main bundle size but I will be able to ensure that AngularFireAuth is initialized first, if it's been provided.

Also I could see filing this as a bug with the Firebase JS SDK... if you initialize Database/Firestore with useEmulator (before you initialize Auth) it seems like it doesn't make sense to error and destroy emulated Auth credentials. At the very least it could catch the 400 and just warn if useEmulator had been called or test if they were emulated credentials and proceed using them for the emulator.

FWIW Storage doesn't seem to have the same problem.

@Feiyang1 any thoughts?

FYI @jhuleatt we're going to run into a similar issue with ReactFire and useEmulator since it's completely lazy loaded.

In thinking about it more, I consider this unexpected behavior on the JS SDK side and have filed an issue.

In thinking about it more, I consider this unexpected behavior on the JS SDK side and have filed an issue.

Yeah, when I tried reproducing to isolate the cause, I didn't encounter the bug when using the vanilla SDK, so I figured this was about AngularFire and create the issue here.

@jornetsimon yeah, it's a race condition. Since we're dynamically importing auth AngularFire is much more likely to see it.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

martinyoussef picture martinyoussef  路  3Comments

aucevica picture aucevica  路  3Comments

KLiFF2606 picture KLiFF2606  路  3Comments

jteplitz picture jteplitz  路  3Comments

Leanvitale picture Leanvitale  路  3Comments