firebase 身份验证在刷新时延迟
firebase auth delayed on refresh
我的 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
以确定身份验证状态。当它 returns 时, 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);
};
两条log语句都执行了,但是react-router replace
好像没有正确执行。也许这对反应路由器专家来说是一个不同的问题。
更新 2:
我做这个的时候已经是深夜了。显然 setTimeout
实际上也不起作用。
好的。因此,我能够通过利用 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 }
});
}
}
};
虽然这是一个与 ReactJS 相关的 post,但我最近在为 AngularJS 编写自己的 authentication/authorisation 服务时遇到了同样的问题。在页面刷新时,onAuthStateChanged
传递了一个 null
的用户,因为 firebase 仍在初始化(异步)。
唯一对我有用的解决方案是在用户登录后将用户 uid 存储在 localStorage
中,并在用户注销后删除该值。
因为我分别使用 authService
和 userService
我在 authService
中注册了一个监听器,一旦用户登录 in/out.
代码示例 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
}
}]);
并且在 运行 块中,您可以全局注册用户并解析 user-info/details 每次路由更改(使其更安全):
.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 工作。这是我如何做的例子。
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});
});
}
这对我有用尝试根据 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> )
}
}
我的 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
以确定身份验证状态。当它 returns 时, 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);
};
两条log语句都执行了,但是react-router replace
好像没有正确执行。也许这对反应路由器专家来说是一个不同的问题。
更新 2:
我做这个的时候已经是深夜了。显然 setTimeout
实际上也不起作用。
好的。因此,我能够通过利用 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 }
});
}
}
};
虽然这是一个与 ReactJS 相关的 post,但我最近在为 AngularJS 编写自己的 authentication/authorisation 服务时遇到了同样的问题。在页面刷新时,onAuthStateChanged
传递了一个 null
的用户,因为 firebase 仍在初始化(异步)。
唯一对我有用的解决方案是在用户登录后将用户 uid 存储在 localStorage
中,并在用户注销后删除该值。
因为我分别使用 authService
和 userService
我在 authService
中注册了一个监听器,一旦用户登录 in/out.
代码示例 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
}
}]);
并且在 运行 块中,您可以全局注册用户并解析 user-info/details 每次路由更改(使其更安全):
.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 工作。这是我如何做的例子。
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});
});
}
这对我有用尝试根据 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> )
}
}