question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Firestore emulator calls Google APIs to check auth token

See original GitHub issue

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.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:17 (7 by maintainers)

github_iconTop GitHub Comments

10reactions
newablecommented, Mar 17, 2021

@WUKS87 you can try with something like:

export function initializeApp(afAuth: AngularFireAuth): () => Promise<null> {
  return () => {
    return new Promise((resolve) => {
      if (!environment.emulators) {
        return resolve();
      } else {
        afAuth.useEmulator(`http://${location.hostname}:9099/`).then(() => {
          resolve();
        });
      }
    });
  };
}

@NgModule({
  ...
  providers: [
    {
      provide: APP_INITIALIZER,
      multi: true,
      deps: [AngularFireAuth],
      useFactory: initializeApp
    },
    { provide: USE_AUTH_EMULATOR, useValue: environment.emulators ? ['localhost', 9099] : undefined },
    { provide: USE_FIRESTORE_EMULATOR, useValue: environment.emulators ? ['localhost', 8080] : undefined },
    { provide: USE_FUNCTIONS_EMULATOR, useValue: environment.emulators ? ['localhost', 5001] : undefined },
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}
3reactions
amilorcommented, Apr 4, 2021

The solution provided by @newable works as long as you can make sure that afAuth.useEmulator(..) is called before any of the AngularFire modules (Firestore, Database, etc.) is initialized. This solution breaks when you want to use for example Firestore in an APP_INITIALIZER factory, or when using NgRx Effects, in which you likely have AngularFirestore injected in the constructor. The problem is that you cannot control in what order the APP_INITIALIZER’s dependencies are instantiated and also it doesn’t really matter, because at the point in time when your APP_INITIALIZER with afAuth.useEmulator(..) is running, the other AngularFire module (be it Firestore that you want to inject in a different APP_INITIALIZER ) has already been created before you manage to call afAuth.useEmulator(..). NgRx Effects on other hand are being instantiated even before the APP_INITIALIZERs are done, so in case of NgRx Effects, you are out of luck even with an APP_INITIALIZER. I have seen a workaround for NgRx Effects where you provide the effects in an injection token after the APP_BOOTSTRAP_LISTENER but again this leads to issues where the effects are registered too late and also providing working feature effects seemed to be an issue.

What I resorted to was the following and it takes care of both issues.

  1. Obtain the Firebase configuration before the application is bootstrapped in the main.ts
  2. Initialize the Firebase app with resolved configuration using the Firebase SDK
  3. If you want to use Firebase Auth emulator, call firebase.auth().useEmulator(..) after you initialize the Firebase app
  4. Provide the Firebase configuration using the AngularFire FIREBASE_OPTIONS token in the platformBrowserDynamic extraProviders argument
  5. Bootstrap your application
  6. In your app or core module (the module where you import AngularFire) import only AngularFireModule without the initializeApp(..) call
  7. Import other AngularFire modules as normal
  8. For using emulators with other AngularFire modules use the provided injection tokens as normal
  9. Do not provide USE_EMULATOR injection token for AngularFireAuthModule

The Firebase SDK initialization is not needed when Auth emulator is not being used, so in production builds the firebase-init-app.ts can be replaced with firebase-init-app.prod.ts that exports an empty initialize function. In that way, you can rely on the dynamic import provided by AngularFire when Auth emulator is not needed.

main.ts

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { FIREBASE_OPTIONS } from '@angular/fire';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { load as loadFirebaseConfig } from './firebase-fetch-config';
import { initialize as initializeFirebaseApp } from './firebase-init-app';

if (environment.production) {
  enableProdMode();
}

loadFirebaseConfig()
  .then((config) => {
    initializeFirebaseApp(config);

    return platformBrowserDynamic([
      {
        provide: FIREBASE_OPTIONS,
        useValue: config,
      },
    ]).bootstrapModule(AppModule);
  })
  .catch((err) => console.error(err));

firebase-init-app.ts

import firebase from 'firebase';
import { environment } from './environments/environment';

export const initialize = (config: any) => {
  firebase.initializeApp(config);

  if (environment.firebase.emulators?.auth) {
    firebase.auth().useEmulator(environment.firebase.emulators.auth);
  }
};

core.module.ts

import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';

import { AngularFireModule } from '@angular/fire';
import { AngularFireAuthModule } from '@angular/fire/auth';
import { AngularFirestoreModule, USE_EMULATOR as FIRESTORE_EMULATOR_CONFIGURATION } from '@angular/fire/firestore';

import { AppStoreModule } from '@app-store/app-store.module';
import { TranslocoRootModule } from '../transloco/transloco-root.module';
import { environment } from '../../environments/environment';

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    HttpClientModule,

    TranslocoRootModule,

    AppStoreModule,

    AngularFireModule,
    AngularFirestoreModule,
    AngularFireAuthModule,
  ],
  providers: [
    {
      provide: FIRESTORE_EMULATOR_CONFIGURATION,
      useValue: environment.firebase.emulators?.firestore,
    },
  ],
})
export class CoreModule {}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Connect your app to the Authentication Emulator - Firebase
Create, update and manage emulated user accounts for testing email/password, phone number/SMS, SMS multi-factor, and third-party (e.g. Google) identity provider ...
Read more >
APIs Explorer - Google Developers
The Google APIs Explorer is a tool available on most REST API reference documentation pages that lets you try Google API methods without...
Read more >
Testing security rules | Firestore - Google Cloud
Requests created via the returned context will not have Firebase Auth tokens attached. Use the returned test context object in your tests to...
Read more >
Firestore Emulator REST API authentication - Stack Overflow
This matches the behavior of the real API. When you send a request without an auth token, you're attempting to authenticate through the ......
Read more >
Using Firebase Authentication in Tests with the ... - Axel Hodler
To support testing Firebase offers Firebase emulators to test the ... to fetch a valid Google OAuth2 access token with the following error:...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found