firebase 身份验证在刷新时延迟

Posted

技术标签:

【中文标题】firebase 身份验证在刷新时延迟【英文标题】:firebase auth delayed on refresh 【发布时间】:2016-09-19 03:07:43 【问题描述】:

我的 SPA React/Firebase 应用程序的硬刷新不会在立即执行功能时保持身份验证状态。我有一个解决方法,但它很粗略。

我的反应路由使用onEnter 函数来确定用户是否经过身份验证。比如

<Route path="/secure" component=Dashboard onEnter=requireAuth/>

此外,我的requireAuth 函数如下所示:

function (nextState, replace) 
        console.log('requireAuth', firebase.auth().currentUser);
        if (!firebase.auth().currentUser) 
            console.log('attempting to access a secure route. please login first.');
            replace(
                pathname: '/login',
                state:  nextPathname: nextState.location.pathname 
            );
        
;

但是,在硬刷新时,firebase.auth().currentUser 会有一点延迟。一开始它是空的,然后对firebase服务器执行POST以确定身份验证状态。当它返回时,currentUser 对象被填充。但是,这种延迟会导致问题。

我的 hacky 解决方案如下:更新:这实际上不起作用...

function (nextState, replace) 
    setTimeout(function () 
        console.log('requireAuth', firebase.auth().currentUser);
        if (!firebase.auth().currentUser) 
            console.log('attempting to access a secure route. please login first.');
            replace(
                pathname: '/login',
                state:  nextPathname: nextState.location.pathname 
            );
        
    , 50);
;

只需将其包装在超时中即可。但是,我真的不喜欢这样……有什么想法吗?

更新:

我还尝试将它封装在 onAuthStateChanged 侦听器中,这应该比带有明确时间延迟的 setTimeout 更准确。代码如下:

function (nextState, replace) 
    var unsubscribe = firebase.auth().onAuthStateChanged(function (user) 
        if (!user) 
            console.log('attempting to access a secure route');
            replace(
                pathname: '/login',
                state:  nextPathname: nextState.location.pathname 
            )
            console.log('should have called replace');
        
        unsubscribe();
    );
    // setTimeout(function () 
    //     console.log('requireAuth', firebase.auth().currentUser);
    //     if (!firebase.auth().currentUser) 
    //         console.log('attempting to access a secure route. please login first.');
    //         replace(
    //             pathname: '/login',
    //             state:  nextPathname: nextState.location.pathname 
    //         );
    //     
    // , 50);
;

两条日志语句都执行了,但是react-routerreplace好像没有正确执行。对于 react-router 专家来说,这可能是一个不同的问题。

更新 2:

我做这个的时候已经很晚了。显然setTimeout 实际上也不起作用。

【问题讨论】:

我想我刚刚在这里回答了一个类似的问题:***.com/questions/37370462/… @FrankvanPuffelen 我实际上尝试过类似的方法。从 requireAuth 函数中将内容包装在 firebase.auth().onAuthStateChanged(function (user) ...);但是,在这种情况下,react-router 的替换功能似乎没有做任何事情。我会更新问题。 【参考方案1】:

好的。因此,我能够通过利用 firebase 提供的 localStorage 变量来存储用户信息来解决这个问题。

function (nextState, replace) 
    if (!firebase.auth().currentUser) 
        let hasLocalStorageUser = false;
        for (let key in localStorage) 
            if (key.startsWith("firebase:authUser:")) 
                hasLocalStorageUser = true;
            
        
        if (!hasLocalStorageUser) 
            console.log('Attempting to access a secure route. Please authenticate first.');
            replace(
                pathname: '/login',
                state:  nextPathname: nextState.location.pathname 
            );
        
    
;

【讨论】:

如果我去你的网站,打开控制台并写localStorage.set("firebase:authUser:123123123", "123123")会发生什么? Firebase 现在似乎在使用索引数据库,所以这个 hack 不再起作用了。【参考方案2】:

虽然这是一篇与 ReactJS 相关的帖子,但我最近在为 AngularJS 编写自己的身份验证/授权服务时遇到了同样的问题。在页面刷新时,onAuthStateChanged 传递了一个用户 null,因为 firebase 仍在初始化(异步)。

唯一对我有用的解决方案是在用户登录后将用户 uid 存储在 localStorage 中,并在用户注销后删除该值。

由于我分别使用authServiceuserService,我在authService 中注册了一个侦听器,一旦用户登录/注销就会触发。

代码示例 authService(不是完整的 authService):

var loginListeners = [];
var logoutListeners = [];

function addLoginListener(func) 
    loginListeners.push(func);


function addLogoutListener(func) 
    logoutListeners.push(func);


function login(email, password) 
    return firebase.auth().signInWithEmailAndPassword(email, password).then(function(user) 
        for(var i = 0; i < loginListeners.length; i++) 
            loginListeners[i](user); // call registered listeners for login
        
    );


function logout() 
    return firebase.auth().signOut().then(function() 
        for(var i = 0; i < logoutListeners.length; i++) 
            logoutListeners[i](); // call registered listeners for logout
        
    );

代码示例 userService(不是完整的 userService):

.provider('userService', ['authServiceProvider',
function UserService(authServiceProvider) 

var usersRefUrl = '/users';
var userInfo = null;
var userDetails = null;

// refreshHack auto-executed when this provider creates the service
var storageId = 'firebase:uid'; // storing uid local because onAuthStateChanged gives null (when async initializing firebase)
(function addRefreshHackListeners() 
    authServiceProvider.addLoginListener(function(user) 
        userInfo = user;
        localStorage.setItem(storageId, user.uid); // store the users uid after login so on refresh we have uid to retreive userDetails
    );
    authServiceProvider.addLogoutListener(function() 
        userInfo = null;
        localStorage.removeItem(storageId);
    );
    firebase.auth().onAuthStateChanged(function(user) 
        if(user)  // when not using refreshHack user is null until async initializing is done (and no uid is available).
            localStorage.setItem(storageId, user.uid);
            userInfo = user;
            resolveUserDetails();
         else 
            localStorage.removeItem(storageId);
            userInfo = null;
            userDetails = null;
        
    );
)();

function isLoggedIn() 
    return userInfo ? userInfo.uid : localStorage.getItem(storageId); // check localStorage for refreshHack


function resolveUserDetails() 
    var p = null;
    var uid = isLoggedIn();
    if(uid)
        p = firebase.database().ref(usersRefUrl + '/' + uid).once('value').then(function(snapshot) 
            userDetails = snapshot.val();
            return userDetails;
        ).catch(function(error) 
            userDetails = null;
        );

    return p; // resolve by returning a promise or null

]);

并且在运行块中,您可以全局注册用户并解决每次路线更改的用户信息/详细信息(使其更安全):

.run(['$rootScope', 'userService', 'authService',
function($rootScope, userService, authService) 

// make user available to $root in every view
$rootScope.user = userService.getUser();

$rootScope.$on('$routeChangeStart',
        function(event, next, current) 

    // make sure we can add resolvers for the next route
    if(next.$$route) 
        if(next.$$route.resolve == null)
            next.$$route.resolve = ;

        // resolve the current userDetails for every view
        var user = userService.resolveUserDetails();
        next.$$route.resolve.userDetails = function() 
            return user;
        
    
);
]);

也许这可以帮助遇到同样问题的人。除此之外,请随意优化和讨论代码示例。

【讨论】:

如果在单独的本地存储变量中,为什么需要存储用户?它已经在那里了。否则很好的角度解决方案。 这只是设计选择的问题。当然,您可以将整个用户保存在 localStorage 中,但我不喜欢对 localStorage 的很多引用。在我的解决方案中,firebase 引用中具有特定的用户信息(详细信息),例如“/users”,这允许监听“child_added”事件或由 firebase 推送的任何其他事件。如果在您的情况下这对您来说是一种开销,您可以选择不同的方法。【参考方案3】:

通过管理 localStorage 工作。这是我如何做的例子。

  constructor(props) 
    super(props);

    let authUser = null;

    // setting auth from localstorage
    for (let key in localStorage) 
      if (key === storageId) 
        authUser = ;
        break;
      
    

    this.state = authUser;
  

  componentDidMount() 
    firebase
      .auth
      .onAuthStateChanged(authUser => 

        if (authUser) 
          localStorage.setItem(storageId, authUser.uid);
         else 
          localStorage.removeItem(storageId);
        

        // change state depending on listener
        authUser
          ? this.setState(authUser)
          : this.setState(authUser: null);
      );
  

【讨论】:

【参考方案4】:

这对我有用,尝试根据 firebase.auth 的状态将您的主应用程序包装在 if else 语句中。

 constructor() 
    super();
    this.state=
    user:,
    stateChanged:false
    ;
   
componentDidMount()
fire.auth().onAuthStateChanged((user)=>
      this.setState(stateChanged:true)
    );

render()  
    if(this.state.stateChanged==false)
      return(
        <div>
          Loading
        </div>
      )
    
else
    
        return(<div>your code goes here...</div> )


【讨论】:

以上是关于firebase 身份验证在刷新时延迟的主要内容,如果未能解决你的问题,请参考以下文章

Firebase 身份验证不持久

firebase 身份验证超时

使用刷新令牌对Firebase管理员进行身份验证

如何在 vue 上处理 Firebase 身份验证

如果有人窃取了您的 Firebase 身份验证刷新令牌,会发生啥情况?

Firebase 身份验证在页面重新加载时在 nuxtjs 中间件中为空