My Angular project successfully uses the AngularFireAuth injectable from @angular/fire to connect to a local Firebase authentication emulator. I used these sources as references for my implementation:
Firebase Docs: Instrument your app to talk to the Authentication emulator
StackOverflow: How to configure AngularFireAuthModule and/or AngularFireAuth to point to auth emulator
However, as soon as I reload the page the login state is lost and I have to log in again.
First I tried setting the persistence like this, in an Angular component constructor:
constructor( private _fireauth: AngularFireAuth) {
this._fireAuth.useEmulator(`http://${location.hostname}:9099/`);
this._fireauth.setPersistence(firebase.auth.Auth.Persistence.LOCAL);
}
which made no difference. Then I noticed in the browser network console that apparently the firebase library does find the locally stored session after pageload but initially attempts to contact google servers to verify it, which of course fails, because it's a local emulator session:
This seemed to make sense because _fireAuth.useEmulator() is only called in the component constructor. Other parts of the app such as the AngularFireAuthGuard probably try accessing the authentication status way earlier than that. A few attempts to call useEmulator() before any other part of the app might attempt to access the authentication status failed:
Attempt1:CallinguseEmulator()in an@Injectable({providedIn: 'root'})aka service constructorAttempt2:Providing anAPP_INITIALIZERwhich executes code before the application is even bootstrapped:
export function initializeApp1(afa: AngularFireAuth): any {
return () => {
return new Promise(resolve => {
afa.useEmulator(`http://${location.hostname}:9099/`);
setTimeout(() => resolve(), 5000);
});
};
}
@NgModule({
declarations: [
...
],
imports: [
...,
AngularFireModule.initializeApp(environment.firebase),
AngularFireAuthModule,
...
],
providers: [
AngularFireAuth,
{
provide: APP_INITIALIZER,
useFactory: initializeApp1,
deps: [AngularFireAuth],
multi: true
}
],
bootstrap: [AppComponent]
})
Attempt3:Providing theUSE_EMULATORInjectable which seems to be implemented in theAngularFireAuthconstructor:
providers: [
{
provide: USE_EMULATOR,
useValue: [location.hostname, 9099]
},
]
I thought especially Attempt3 would set the emulator mode as early as possible because how much earlier could it be set than in the constructor? The Injectable seem to work, as the browser console prints the usual warning message on pageload: auth.esm.js:133 WARNING: You are using the Auth Emulator, which is intended for local testing only. Do not use with production credentials.. However, the request to googleapis.com is still fired every time a stored session in the local indexedDB is detected on pageload and I'm logged out afterwards. Also worth noting: In Attempt2 I use a 5 second timeout to slow down the app initialization noticeably and the request to googleapis.com is fired instantly after pageload, way before Angular has finished initializing.
At this point I don't know what to try anymore. @angular/fire is obviously build upon the basic Firebase JavaScript library, so I might have to set emulator mode even earlier, but maybe I'm walking in the completely wrong direction here?
================================================================
[EDIT:] It seems that combining Attempt 2 with Attempt 3 works, as long as the initializeApp1() method uses a setTimout() of at least approx. 100ms. When I remove the timeout or set it to something as low as 10ms the login ist lost. Any timeout longer than 100ms works too. At this point it looks very much like a race condition?
Working example:
export function initializeApp1(afa: AngularFireAuth): any {
return () => {
return new Promise(resolve => {
afa.useEmulator(`http://${location.hostname}:9099/`);
setTimeout(() => resolve(), 100); // delay Angular initialization by 100ms
});
};
}
@NgModule({
declarations: [
...
],
imports: [
...,
AngularFireModule.initializeApp(environment.firebase),
AngularFireAuthModule,
...
],
providers: [
// Provide the AngularFireAuth constructor with the emulator parameters
{
provide: USE_EMULATOR,
useValue: [location.hostname, 9099]
},
// Delay the app initialization process by 100ms
{
provide: APP_INITIALIZER,
useFactory: initializeApp1,
// for some reason this dependency is necessary for this solution to work.
// Maybe in order to trigger the constructor *before* waiting 100ms?
deps: [AngularFireAuth],
multi: true
}
],
bootstrap: [AppComponent]
})
I also noticed this comment in the AngularFireAuth source code. So far I don't really understand what's happening, but maybe the issue is related:
// HACK, as we're exporting auth.Auth, rather than auth, developers importing firebase.auth (e.g,
import { auth } from 'firebase/app') are getting an undefined auth object unexpectedly as we're completely lazy. Let's eagerly load the Auth SDK here. There could potentially be race conditions still... but this greatly decreases the odds while we reevaluate the API.

AngularFireAuthinjectable. - codepearlex