试图让 firebase.auth().currentUser 成为一个承诺

Posted

技术标签:

【中文标题】试图让 firebase.auth().currentUser 成为一个承诺【英文标题】:Attempting to make firebase.auth().currentUser a promise 【发布时间】:2020-11-12 18:53:03 【问题描述】:

当用户登录时,我试图将他们发送到受限页面。所以我通过检查用户对象来保护路由。问题是当用户创建或登录时,auth 不会立即更改,因此在创建或登录后,firebase.auth().currentUser 可以在几毫秒内返回null。因此,如果我将它们发送到页面,它将返回错误。

这就是我试图让路由等待一段时间以查看身份验证是否更改的尝试。我想知道代码是否有任何问题或编写此代码的更好方法。

附:我知道我可以检查firebase.auth().onAuthStateChanged,但我不明白如何在用户创建或登录时使用它。它只是在后台运行,当身份验证更改时我不能只向用户发送路由没有语境。它也没有超时。

getUser(  commit  ) 
  return new Promise( ( resolve, reject )=> 
    let user, i = 0
    function checkForUser() 
      setTimeout( ()=> 
        i++
        user = firebase.auth().currentUser
        if ( user ) 
          router.push(  path: '/restricted'  )
          resolve()
         else if ( i > 30 ) 
          reject(  message: 'Something went wrong, please try again.'  )
         else 
          checkForUser()
        
      , 100 )
     
   checkForUser()
   )
,

【问题讨论】:

似乎您还想显示登录用户的代码,因为您在此处显示的内容取决于其他代码,以及您可能如何处理其结果。跨度> 有一个很长的 GitHub 问题(他们需要很长时间才能修复 IMO),列出了一些很好的解决方法 ~ github.com/firebase/firebase-js-sdk/issues/462 @DougStevenson 我实际上在需要用户对象存在的几个不同地方使用它。所以我可以运行这个函数并等待它通过或失败。但不知何故感觉有点乱,想知道是否有更清洁的方法来做到这一点。 @Phil 我已经看到了线程,但解决方案似乎没有处理超时,如果用户对象没有被填充,就会卡住。 【参考方案1】:

这是一个很长的答案,因为 Firebase 尚未就 https://github.com/firebase/firebase-js-sdk/issues/462 做任何事情


这是我过去的做法,在 Vuex 中同步用户。

为此,我有两个突变和一个方便的吸气剂,但那里没有什么特别的

state: 
  user: null
,
getters: 
  isAuthenticated: state => typeof state.user === 'object' && state.user !== null
,
mutations: 
  LOGIN: (state, user) => (state.user = user),
  LOGOUT: state => (state.user = null)

我有一个 firebase/index.js 文件,用于配置如下所示的 Firebase 应用程序

import firebase from 'firebase/app'
import 'firebase/auth'
// import any other Firebase libs like firestore, etc
import config from './config'
import store from '../store' // import the Vuex store

firebase.initializeApp(config)

export const auth = firebase.auth()
// export other Firebase bits like db, etc

const onAuthStateChangedPromise = new Promise((resolve, reject) => 
  auth.onAuthStateChanged(user => 
    store.commit(user !== null ? 'LOGIN' : 'LOGOUT', user)
    resolve(user)
  , err => 
    reject(err)
  )
)

export const onAuthStateInit = () => onAuthStateChangedPromise

onAuthStateChanged 监听器使 store 与用户的 auth 状态保持同步,但外部 Promise 仅解析一次(随后对 resolve() 的调用将被忽略)。由于 Promise 的这一特性,onAuthStateChangedPromise 可用于检测 Firebase 身份验证系统何时完成其初始化阶段。

然后我将该承诺公开为一个名为 onAuthStateInit 的函数。

在我的路由器中,我使用名为 publicmeta 标志来确定路由是否可公开访问(大多数路由没有它,这意味着它们需要身份验证)。

我的全局导航守卫看起来像这样

import  onAuthStateInit  from './firebase'

// define routes, new VueRouter, etc

router.beforeEach(async (to, from, next) => 
  await onAuthStateInit() // wait for auth system to initialise
  if (to.matched.every(route => route.meta.public) || store.getters.isAuthenticated) 
    next()
   else 
    next( name: 'sign-in' ) // redirect to sign-in page
  
)

您可以在需要等待首页加载身份验证初始化完成的任何地方使用await onAuthStateInit()。您可以根据需要多次调用它,它只会等待一次。身份验证初始化完成后,此承诺将立即解决。

我的 sign-in 页面使用 Firebase 身份验证 UI,并定义了以下 回调

import firebase from 'firebase/app'
import * as firebaseui from 'firebaseui'
import  auth  from '../firebase'
import 'firebaseui/dist/firebaseui.css'

const ui = new firebaseui.auth.AuthUI(auth)

export default 
  mounted () 
    // the ref is just a <div> in my template
    const container = this.$refs.firebaseuiAuthContainer
    ui.start(container,  
      // signInOptions, etc
      callbacks: 
        signInSuccessWithAuthResult: authResult => 
          this.$router.push( name: 'home' ) // go to homepage or wherever
        ,
        signInFailure: err => 
          // handle sign-in error
        
      
    )    
  

您不必使用身份验证 UI。 onAuthStateChange 侦听器将捕获对当前用户身份验证状态的任何更改,因此您可以根据需要使用手动 auth.SignInWith*() 方法。

【讨论】:

为什么 onAuthStateInit 只运行一次?它不是每次都创建一个新的承诺吗? @KelvinZhao 不,你可以看到它返回了只创建一次的onAuthStateChangedPromise。我只是把它做成一个函数,因为await onAuthStateInit() 看起来比await onAuthStateChangedPromise 更好 啊...所以当你return new Promise,它会在第一次解析时锁定它,而如果你new Promise,它每次都会运行一个新的承诺?是这样的吗? @KelvinZhao 我的回答中没有return new PromiseonAuthStateChangedPromise 是一个 Promise,它只被创建一次。 onAuthStateInit 函数每次调用时都会返回相同的承诺 对不起,最后一个问题,当用户退出,然后重新登录时会发生什么。onAuthStateInit 将返回为真,但仍有一瞬间用户正在登录,因此,如果我在那一瞬间之前将它们指向受限页面,它将弹回主页。【参考方案2】:

您可以将signInWithRedirect 方法与您选择的AuthProvider 以及getRedirectResult 方法一起使用。以 Google 为例:

firebase.auth().onAuthStateChanged(function(user) 
    if (user) 
        firebase.auth().getRedirectResult().then(function(result) 
            if (result.additionalUserInfo && result.additionalUserInfo.isNewUser) 
                // User just created.
            
            if (result.user) 
                // User just signed in.
            
        );
        // User is signed in.
    
    else 
        // User is signed out.
        firebase.auth().signInWithRedirect(new firebase.auth.GoogleAuthProvider());
    
);

更多信息:https://firebase.google.com/docs/reference/js/firebase.auth.Auth#methods

【讨论】:

以上是关于试图让 firebase.auth().currentUser 成为一个承诺的主要内容,如果未能解决你的问题,请参考以下文章

使用 firebase auth 和 firestore 在流中调用未来

如何让 Firebase auth OAuthProvider 使用 OIDC 提供程序添加其他范围

如何在 firebase-functions 中使用 firebase.auth()?

Firebase Auth用户角色[重复]

如何修复 Firebase Auth 仅在刷新后工作?

Firebase Auth 密码重置:如何正确发送包含确认码的密码重置电子邮件?