使用身份验证和用户上下文反应路由

Posted

技术标签:

【中文标题】使用身份验证和用户上下文反应路由【英文标题】:React Routing with Authentication and User Contexts 【发布时间】:2021-05-01 18:10:28 【问题描述】:

我正在尝试找出在我的 React 应用程序中处理身份验证和路由的最佳方法。我使用了Kent Dodds 和also here 概述的方法,其他人扩展了他的方法。

基本上,您有一个用于身份验证的上下文和一个用于经过身份验证的用户信息的上下文。如果用户通过身份验证,则呈现<AuthenticatedApp />,如果不存在用户信息,则呈现<UnauthenticatedApp/>

各个供应商的细分如下:

AppContext.js

const AppProvider = ( children ) => (
  <Router>
    <ApolloProvider client=client>
      <AuthProvider>
        <UserProvider>children</UserProvider>
      </AuthProvider>
    </ApolloProvider>
  </Router>
);

export default AppProvider;

还有我的App.js

function App() 
  const user = useUser();
  return user ? (
    <ThemeProvider theme=theme>
      <GlobalStyle />
      <AuthenticatedApp />
    </ThemeProvider>
  ) : (
    <ThemeProvider theme=theme>
      <GlobalStyle />
      <UnauthenticatedApp />
    </ThemeProvider>
  );

如何最好地处理未验证组件和已验证组件中的各个组件之间的路由?我应该将它们单独包装在&lt;Router&gt; 中还是让&lt;Router 围绕在我的上下文上方的顶层?我目前在其他上下文提供者之上使用第一种方法和反应路由器。

AuthenticatedApp.js

 const RedirectHome = () => <Redirect to="/dashboard" />;

const AuthenticatedApp = () => (
  <Switch>
    <Route exact path="/">
      <RedirectHome />
    </Route>
    <Route path="/dashboard">
      <DashboardNavbar />
      <Dashboard />
    </Route>
    <Route path="/projects">
      <DashboardNavbar />
      <Projects />
    </Route>
    <Route exact path="/*">
      <RedirectHome />
    </Route>
  </Switch>
);
UnauthenticatedApp.js

  <Switch>
    <Route exact path="/">
      <LandingPage RightSide=UserLogin />
    </Route>
    <Route exact path="/login">
      <LandingPage RightSide=UserLogin />
    </Route>
    <Route path="/signup">
      <LandingPage RightSide=UserSignup />
    </Route>
    <Route path="/*">
      <LandingPage RightSide=UserLogin />
    </Route>
  </Switch>

这是最好的方法吗?为了注销,我有以下内容:

AuthContext.js

 const history = useHistory();
  const logout = () => 
    localStorage.removeItem('AUTH_TOKEN');
    refetch();
    history.push('/');
    history.go();
  ;

将历史记录推送到/更改地址但似乎没有更改页面,它仍然位于仪表板页面上,即使删除了 JWT 令牌,我仍然没有收到 &lt;Unauthenticated/&gt; 组件。在发现身份验证缺少 JWT 令牌之前,我必须强制重新加载当前页面。

对不起,我只是很困惑!

更新 #1

我的 useUser 上下文/钩子是:

const UserProvider = (props) => 
  const  data  = useAuth();

  return (
    // eslint-disable-next-line react/jsx-props-no-spreading
    <UserContext.Provider value=data ? data.getWhoAmI : null ...props />
  );
;

const useUser = () => React.useContext(UserContext);

export  UserProvider, useUser ;

我的 AuthContext 是:

const AuthContext = React.createContext();

const AuthProvider = (props) => 
  const  loading, data, refetch  = useQuery(WHOAMI_QUERY);
  const [loginUser] = useMutation(LOGIN_USER_MUTATION);
  const [signupUser] = useMutation(SIGNUP_USER_MUTATION);

  const signin = async (username, password) =>
    loginUser( variables:  username, password  ).then((res) => 
      if (res && res.data && res.data.loginUser && res.data.loginUser.token) 
        const  token  = res.data.loginUser;
        localStorage.setItem('AUTH_TOKEN', token);
        refetch();
       else 
        throw Error('No token returned');
      
      return res;
    );

  const signup = (firstName, lastName, username, email, password) =>
    signupUser(
      variables:  firstName, lastName, username, email, password ,
    ).then((res) => 
      if (res && res.data && res.data.signup && res.data.signup.token) 
        const  token  = res.data.signup;
        localStorage.setItem('AUTH_TOKEN', token);
        refetch();
       else 
        throw Error('No token returned');
      
      return res;
    );

  const history = useHistory();
  const logout = () => 
    localStorage.removeItem('AUTH_TOKEN');
    refetch();
    history.push('/');
    history.go();
  ;

  if (loading) 
    return <p>Loading!</p>;
  
  return (
    // eslint-disable-next-line react/jsx-props-no-spreading
    <AuthContext.Provider value= data, signin, signup, logout  ...props />
  );
;

const useAuth = () => React.useContext(AuthContext);

export  AuthProvider, useAuth ;

【问题讨论】:

乍一看,这里没有什么明显的错误。您带有一个 Router 和两个 Switch 块的结构看起来应该没问题。所以我不确定错误在哪里。 如果我们从“/login”重定向到“/”,这似乎可行。我们是否从“/”(注销的地方)重定向到“/”(登录的地方)?由于路径相同,可能不会触发刷新。 这就是我正在努力解决的问题。我原以为推动历史更改就足够了,但如果只需要在令牌删除和 history.push 之后重新加载页面,那么也许这是一个可以接受的解决方案?只是感觉很笨重。 我已更新 AuthenticatedApp 以显示重定向。登录时,“/”重定向到“/dashboard”。因此,如果我在 /dashboard 上并单击注销,则会删除 JWT 令牌,然后调用 history.push("/"),这将导致我被重定向回“/dashboard”,但没有存储用户 / JWT所以应该渲染 UnauthenticatedApp 组件,带我回到登录页面。 history.push("/") 会将他们重定向到 URL "/",不是吗? 【参考方案1】:

您的 App.js 文件中没有状态更改,因此在您的用户注销后不会重新呈现。您必须使用 useEffect 挂钩或切换到基于类的组件才能包含状态。

here 的最底层代码显示了他们如何通过 props 向下传递登录函数来设置 App.js 中的状态。您的注销功能可以类似地工作。我在我的网站上做过类似的事情。

【讨论】:

我认为useUser 是一个有状态的钩子。如果从const user = useUser(); 返回的user 发生变化,这将导致App 重新渲染并可能从AuthenticatedApp 切换到UnauthenticatedApp 感谢您的帮助,我会尝试一下。这样做而不是按照我上面概述的方式这样做有什么好处? 我已经发布了 useUser 的代码和我的 AuthContext 文件。 注销是否会更改您在 AuthContext 中检索的 data 的值? 我的理解是应该的。我在帖子底部添加了我的 AuthContext 代码。 data 是查询我的 API 以查找与 JWT 中包含的 ID 匹配的用户的响应,该 ID 在注销时被删除。注销会删除令牌并调用 refetch(),然后应该使用新的响应更新 data。【参考方案2】:

因此,在深入研究了 GraphQL 查询 getWhoAmI 之后,似乎当我在 getWhoAmI 查询上调用 refetch() 的注销时,它无法使用 JWT 对请求进行身份验证并找到用户,然后执行throw new AuthenticationError('You do not have permission for this request'); which不更新 data ,但奇怪的是也不返回 error。

如果我将 GraphQL 解析器更改为返回 null 而不是错误,然后我将注销,因为 data 从用户信息更改为 null,这会导致重新渲染。

【讨论】:

以上是关于使用身份验证和用户上下文反应路由的主要内容,如果未能解决你的问题,请参考以下文章

使用 redux 保护的路由和身份验证反应路由器 v4

反应路由器经过身份验证的路由问题

在反应中使用 cookie 的客户端身份验证和受保护的路由

使用私有路由和上下文响应身份验证

使用 Express 会话反应本机身份验证?

如何允许任何用户使用 Laravel JWT 身份验证访问路由?