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
中,并在用户注销后删除该值。
由于我分别使用authService
和userService
,我在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 身份验证在刷新时延迟的主要内容,如果未能解决你的问题,请参考以下文章