使用 firebase auth 刷新受保护的路由 React Router

Refresh on protected Routes React Router with firebase auth

我正在使用 firebase、react 和 redux 构建一个待办事项网络应用程序。所以我已经保护了诸如 "/todos/add""/dashboard" 和访客路由 "/""/signup""/login" 这样的路由,如果你是已通过身份验证。

我的问题是,当我在 "/todos/add" 路由中进行身份验证时,我重新加载应用程序重新加载的页面,并且我的身份验证操作被调度,将我的用户放入 redux 商店,但我被重定向到 "/login"然后到"/dashboard"。但我想在重新加载之前在同一页面上。我的代码:

PrivateRoute.js

import React from "react";
import { connect } from "react-redux";
import { Route, Redirect } from "react-router-dom";
import PropTypes from "prop-types";

const PrivateRoute = ({
  isAuthenticated,
  isLoading,
  component: Component,
  ...rest
}) => (
  <Route
    {...rest}
    render={props =>
      isAuthenticated ? (
        <Component {...props} {...rest} />
      ) : (
        <Redirect to="/login" />
      )
    }
  />
);
const mapStateToProps = state => ({
  isAuthenticated: !!state.user.uid,
  isLoading: state.user.isLoading
 });

PrivateRoute.propTypes = {
  component: PropTypes.func.isRequired,
  isAuthenticated: PropTypes.bool.isRequired,
   isLoading: PropTypes.bool.isRequired
};

export default connect(mapStateToProps)(PrivateRoute);

GuestRoute.js

import React from "react";
import { connect } from "react-redux";
import { Route, Redirect } from "react-router-dom";
import PropTypes from "prop-types";

const GuestRoute = ({ isAuthenticated, component: Component, ...rest }) => (
  <Route
    {...rest}
    render={props =>
      !isAuthenticated ? (
        <Component {...props} />
      ) : (
        <Redirect
          to="/dashboard"
        />
      )
    }
  />
);

const mapStateToProps = state => ({
  isAuthenticated: !!state.user.uid,
  isLoading: !!state.user.isLoading
});

GuestRoute.propTypes = {
  component: PropTypes.func.isRequired,
  isAuthenticated: PropTypes.bool.isRequired,
  isLoading: PropTypes.bool.isRequired
};

export default connect(mapStateToProps)(GuestRoute);

App.js

import React from "react";
import { Route, Switch } from "react-router-dom";

// COMPONENTS
import HomePage from "./components/pages/homepage/HomePage";
import SignupPage from "./components/pages/signuppage/SignupPage";
import LoginPage from "./components/pages/loginpage/LoginPage";
import DashboardPage from "./components/pages/dashboardpage/DashboardPage";

// HOC
import Layout from "./components/hoc/layout/Layout";

// ROUTES
import PrivateRoute from "./routes/PrivateRoute";
import GuestRoute from "./routes/GuestRoute";
import AddTodosPage from "./components/pages/todos/add/AddTodosPage";

const App = () => (
  <Layout>
    <Switch>
      <Route path="/" exact component={HomePage} />
      <GuestRoute path="/signup" exact component={SignupPage} />
      <GuestRoute path="/login" exact component={LoginPage} />
      <PrivateRoute path="/todos/add" exact component={AddTodosPage} />
      <PrivateRoute path="/dashboard" exact component={DashboardPage} />
    </Switch>
  </Layout>
);

export default App;

FIX
isLoading 默认设置为 true,这将是刷新时的值。 (在 firebase 身份验证后将 isLoading 设置为 false。)并且 <PrivateRoute> 中的条件应为 (isLoading || isAuthenticated )
但重要: 这里 isLoadingisAuthenticated 应该在 firebase 身份验证响应后一起设置。或者 isAuthenticated 应在 isLoading 设置为 false 之前相应设置。

说明
一旦您在 /todos/add<PrivateRoute> 中刷新,它就会查找 isAuthenticated 值。这里似乎是 false,因此将被重定向到 /login 路线。
当处于 /login<GuestRoute> 时,它将再次查看 !isAuthenticated 值。此时 isAuthenticated 值似乎是 true 使其重定向回 /dashboard.

所以从这个观察可以看出, isAuthenticated 值并不是一刷新就设置为 true 。而是这个 firebase 身份验证请求是异步操作,其中会有一些值得注意的响应。但在响应之前,重定向会以默认 isAuthenticated 值或 undefined 作为值。 In-between 此 re-directions,将完成 firebase 身份验证调用并将 isAuthenticated 设置为 true 以最终重定向到 /dashboard.

但是这里使用 isLoading 值来延迟重定向。默认情况下将 isLoading 设置为 true,这将是刷新时的值。 (在 firebase 身份验证后将 isLoading 设置为 false。)并且 <PrivateRoute> 中的条件应为 (isLoading || isAuthenticated )重要提示: 这里 isLoadingisAuthenticated 应该在 firebase 身份验证响应后一起设置。或 isAuthenticated 应在 isLoading 设置为 false 之前相应设置。 (否则如果 isLoading 设置为 false 并且如果 isAuthenticated 仍然是 falseundefined 它将被重定向!)

不要渲染您的路线,而是尝试渲染类似 'a loading state' 的内容。 否则,如果身份验证调用需要一些时间来解决,您最终将向未经身份验证的用户显示您的私有路由。

const PrivateRoute = ({
  component: Component,
  permission: Permission,
  waitingForAuth: Waiting,
  ...rest
}) => (
  <Route
    {...rest}
    render={props => {
      if (Waiting) return <LinearProgress />;
      return Permission ? <Component {...props} /> : <Redirect to="/" />;
    }}
  />
);

我的堆栈(firebase + vuex + vuejs)也遇到了类似的问题。我的解决方案是在我的商店中添加一个 userLoading 状态,即 Promise<void>;.

我的设计是将 auth 实例传递给我的存储状态构造函数,并在 auth 状态更改时解析 userLoading promise。

我的路由使用 await 作为承诺,然后检查是否有用户。

这是我的状态构造函数:

 public constructor(auth: firebase.auth.Auth) {
    this.user = auth.currentUser;
    this.userLoading = new Promise(resolve => {
        auth.onAuthStateChanged(async (user: firebase.User | null) => {
            if (user) {
                await user!.getIdToken(true);
                const decodedToken = await user!.getIdTokenResult();

                this.stripeRole = decodedToken.claims.stripeRole;
            }

            this.user = user;
            resolve();
        });
    });
}