AngularFireAuth 模拟器登录在页面重新加载时丢失

Posted

技术标签:

【中文标题】AngularFireAuth 模拟器登录在页面重新加载时丢失【英文标题】:AngularFireAuth emulator login is lost on page reload 【发布时间】:2021-03-09 12:12:13 【问题描述】:

我的 Angular 项目成功地使用来自 @angular/fire 的 AngularFireAuth 注入连接到本地 Firebase 身份验证模拟器。我使用这些资源作为我的实现的参考:

Firebase 文档:Instrument your app to talk to the Authentication emulator

堆栈溢出:How to configure AngularFireAuthModule and/or AngularFireAuth to point to auth emulator

但是,一旦我重新加载页面,登录状态就会丢失,我必须重新登录。

首先我尝试在 Angular 组件构造函数中像这样设置持久性:

constructor( private _fireauth: AngularFireAuth) 
    this._fireAuth.useEmulator(`http://$location.hostname:9099/`);
    this._fireauth.setPersistence(firebase.auth.Auth.Persistence.LOCAL);

这没什么区别。然后我在浏览器网络控制台中注意到,显然 firebase 库在页面加载后确实找到了本地存储的会话,但最初尝试联系谷歌服务器来验证它,这当然失败了,因为它是一个本地模拟器会话:

这似乎是有道理的,因为_fireAuth.useEmulator() 只在组件构造函数中被调用。应用程序的其他部分(例如AngularFireAuthGuard)可能会尝试更早地访问身份验证状态。在应用程序的任何其他部分可能会尝试访问身份验证状态之前调用 useEmulator() 的几次尝试失败:

Attempt1:@Injectable(providedIn: 'root') 又名服务构造函数中调用 useEmulator() Attempt2: 提供一个 APP_INITIALIZER 在应用程序启动之前执行代码:
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:AngularFireAuth 构造函数中提供USE_EMULATOR 可注入的seems to be implemented:
  providers: [
     
       provide: USE_EMULATOR,
       useValue: [location.hostname, 9099]
     ,
  ]

我特别认为Attempt3 会尽可能早地设置模拟器模式,因为它可以比在构造函数中设置早多少? Injectable 似乎可以工作,因为浏览器控制台会在页面加载时打印通常的警告消息:auth.esm.js:133 WARNING: You are using the Auth Emulator, which is intended for local testing only. Do not use with production credentials.。但是,每次在页面加载时检测到本地 indexedDB 中的存储会话时,仍然会触发对 googleapis.com 的请求,然后我就退出了。另外值得注意的是:在 Attempt2 中,我使用 5 秒超时来显着减慢应用程序初始化速度,并且在页面加载后立即触发对 googleapis.com 的请求,在 Angular 完成初始化之前。

此时我不知道该尝试什么了。 @angular/fireis obviously build upon the basic Firebase javascript library,所以我可能不得不更早地设置模拟器模式,但也许我在这里走错了方向?

================================================ ==================

[编辑:] 似乎将Attempt 2Attempt 3 结合起来是可行的,只要initializeApp1() 方法使用的setTimout() 至少约为。 100 毫秒。当我删除超时或将其设置为低至 10 毫秒时,登录就会丢失。任何超过 100 毫秒的超时也有效。在这一点上,它看起来很像竞态条件?

工作示例:

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]
)

我还注意到AngularFireAuth 源代码中的this comment。到目前为止,我真的不明白发生了什么,但也许问题是相关的:

// HACK,因为我们正在导出 auth.Auth,而不是 auth,导入 firebase.auth(例如,import auth from 'firebase/app')的开发人员会意外地得到一个未定义的 auth 对象,因为我们完全是懒惰的。让我们在这里急切地加载 Auth SDK。可能仍然存在竞争条件……但这大大降低了我们重新评估 API 的几率。

【问题讨论】:

在同样的问题上苦苦挣扎。 auth 模拟器相当新,它可能是需要在其中一个库中纠正的错误/缺失功能。 我刚刚添加了一些新见解。它看起来像是 AngularFireAuth 可注入的构造函数中的竞争条件。 我也发现了那个评论,但是调试多了一点后我更加不确定了。 其实是buggithub.com/firebase/firebase-js-sdk/issues/4110 我设法通过清理我的依赖项来绕过它,以便在身份验证后初始化 firestore。我发现我的自定义身份验证服务依赖于 firestore。 【参考方案1】:

以下解决方案对我有用 -

将此添加到 app.module.ts 的顶部(@NgModule 之前)-

const firebaseConfig = 
  ...
;

import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/auth';
import 'firebase/functions';

const app = firebase.initializeApp(firebaseConfig, 'myApp');
if (environment.useEmulators) 
  app.auth().useEmulator('http://localhost:9099');
  app.firestore().useEmulator('localhost', 8080);
  app.functions().useEmulator('localhost', 5001);

然后在您的 imports 部分进一步向下:

  imports: [
    ...
    AngularFireModule.initializeApp(firebaseConfig, 'myApp'),
    ...
  ],

我猜这个方法比 AngularFire 更早地“获取”了 Auth 服务。但正如您所提到的,身份验证模拟器是相当新的,希望在下一个版本中它会得到修复。基于this github post。

【讨论】:

我按照你的步骤,好像和github.com/firebase/firebase-js-sdk/issues/4110差不多。但是,我的身份验证似乎仍在云端。我正在使用 Angular 9.1.13、Firebase 8.9.1 和 AngularFire 6.1.5。【参考方案2】:

我已经在我的代码中调整了初始化应用程序函数,以使用 rxjs 而不是 Promise。

export const initializeApp = (angularFireAuth: AngularFireAuth): (() => Observable<boolean>) => 
    if (!environment.firebaseConfig.useEmulator) 
        return () => of(true);
     else 
        return () => of(true).pipe(
            delay(100),
            tap(_ => 
                angularFireAuth.useEmulator('http://localhost:9099/');
            )
        );
    
;

【讨论】:

以上是关于AngularFireAuth 模拟器登录在页面重新加载时丢失的主要内容,如果未能解决你的问题,请参考以下文章

python requests 爬虫模拟登录后访问一些界面还是会重定向到登录界面?

未捕获(承诺):错误:没有 AngularFireAuth 的提供者

无法在 AngularFireAuth 中捕获异常

模拟登录博客园

angularFireAuth 不适用于谷歌

使用 MySQL 的 Android 登录页面上的用户名和密码错误