在我开始使用 ReactJs 构建全栈 Web 应用程序时,我发现自己对如何在前端处理身份验证感到困惑。我的意思是,从后端收到访问令牌后,您下一步应该做什么?如何保留登录状态?
大多数初学者会假设“哦,只需将您的令牌存储在状态中即可”。但我很快发现这不是最好的解决方案,甚至根本不是一个解决方案,因为正如大多数经验丰富的 ReactJs 开发人员所知,状态是暂时的,因为每次刷新页面时它都会被清除,我们绝对可以'用户每次刷新都不需要登录。
快进到现在,我已经获得了一些在 React 中构建全栈应用程序的经验,研究了更有经验的开发人员的身份验证方法并在其他两个应用程序中复制了该过程,我想提供一个指南关于我目前如何处理它。有些人可能认为这不是最好的方法,但我现在已经采用它作为我的方法,并且我愿意学习其他开发人员使用的其他方法。
您已将您的电子邮件和密码(假设您使用基本电子邮件和密码身份验证)提交到后端以启动身份验证过程。我不会谈论后端如何处理身份验证,因为本文是关于如何仅在前端处理身份验证。我将跳到您在 HTTP 响应中收到令牌的部分。下面是一个简单登录表单组件的代码示例,该组件将电子邮件和密码提交到服务器并在响应中接收令牌和用户信息。现在为了简单起见,我的表单值是通过状态来管理的,对于生产应用程序来说,使用像 formik 这样强大的库会更好。
import axios from 'axios' import { useState } from "react" export default function LoginForm() { const [email, setEmail] = useState("") const [password, setPassword] = useState("") const handleSubmit = async() => { try { const response = await axios.post("/api/auth/login", { email, password }) if (response?.status !== 200) { throw new Error("Failed login") } const token = response?.data?.token const userInfo = response?.data?.userInfo } catch (error) { throw error } } return() }
包装整个应用程序,或仅包装需要访问身份验证上下文提供程序中的身份验证状态的部分。这通常在根 App.jsx 文件中完成。如果您不知道 context API 是什么,请随时查看 Reactjs 文档。下面的示例显示了创建的 AuthContext 提供程序组件。然后将其导入到 App.jsx 中并用于包装 App 组件中返回的 RouterProvider,从而使身份验证状态可以从应用程序中的任何位置访问。
import { createContext } from "react"; export const AuthContext = createContext(null) export default function AuthProvider({children}) { return({children} ) }
import React from "react"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import AuthProvider from "./AuthContext"; const router = createBrowserRouter([ // your routes here ]) function App() { return() } export default App
在身份验证上下文中,您必须初始化两个状态变量“isLoggedIn”和“authenticatedUser”。第一个状态是布尔类型,最初设置为“false”,然后在确认登录后更新为“true”。第二个状态变量用于存储登录用户的信息,例如姓名、电子邮件等。这些状态变量必须包含在上下文组件中返回的提供程序的值中,以便可以在整个应用程序中访问它们以进行条件渲染.
import { createContext, useState } from "react"; export const AuthContext = createContext(null) export default function AuthProvider({children}) { const [isLoggedIn, setIsLoggedIn] = useState(false) const [authenticatedUser, setAuthenticatedUser] = useState(null) const values = { isLoggedIn, authenticatedUser, setAuthenticatedUser } return({children} ) }
Nanostores 是一个用于管理 Javascript 应用程序状态的包。该包提供了一个简单的 API,用于管理跨多个组件的状态值,只需在单独的文件中初始化它并将其导入到您想要使用状态或更新状态的任何组件中即可。但是,为了存储在第一步中的 HTTP 响应中收到的身份验证令牌,您将使用 nanostores/persistent。该包通过将状态存储在 localStorage 中来保留您的状态,这样当您刷新页面时它就不会被清除。 @nanostores/react 是针对 Nanostore 的 React 特定集成,它使 useStore 钩子可用于从 nanostore 状态中提取值。
所以现在您可以继续:
安装以下软件包:nanostores、@nanostores/persistent 和 @nanostores/react。
在名为 user.atom.js 或您选择的任何名称的单独文件中,使用 nanostores/persistent 初始化“authToken”存储和“user”存储。
将它们导入到您的登录表单组件文件中,并使用登录响应中收到的令牌和用户数据更新状态。
npm i nanostores @nanostores/persistent @nanostores/react
import { persistentMap } from '@nanostores/persistent' export const authToken = persistentMap('token', null) export const user = persistentMap('user', null)
import { authToken, user } from './user.atom' const handleSubmit = async() => { try { const response = await axios.post("/api/auth/login", { email, password }) if (response?.status !== 200) { throw new Error("Failed login") } const token = response?.data?.token const userInfo = response?.data?.userInfo authToken.set(token) user.set(userInfo) } catch (error) { throw error } }
现在,在包装应用程序的身份验证上下文中,您必须确保令牌和用户状态保持更新并在整个应用程序中可用。要实现这一目标,您必须:
导入“authToken”和“user”存储。
初始化一个 useEffect 钩子,在钩子内部创建一个 'checkLogin()' 函数,该函数将检查令牌是否存在于 'authToken' 存储中,如果存在,则运行一个函数来检查它是否存在已到期。根据检查结果,您可以将用户重定向到登录页面以进行身份验证,或者...将“isLoggedIn”状态设置为 true。现在,为了确保更频繁地跟踪登录状态,可以将此挂钩设置为在每次当前路径更改时运行,这样,如果用户的令牌在与应用程序交互时过期,则用户可能会被踢出或重定向到登录页面。
初始化另一个 useEffect 钩子,该钩子将包含一个函数,用于在每次加载或刷新应用程序时使用 authToken 存储中的令牌从后端获取用户信息。如果您收到成功的响应,请将“isLoggedIn”状态设置为 true,并使用响应中收到的用户信息更新“authenticatedUser”状态和“user”存储。
下面是更新后的AuthProvider组件文件。
import { createContext, useState } from "react"; import { authToken, user } from './user.atom'; import { useStore } from "@nanostores/react"; import { useNavigate, useLocation } from "react-router-dom"; import axios from "axios"; export const AuthContext = createContext(null) export default function AuthProvider({children}) { const [isLoggedIn, setIsLoggedIn] = useState(false) const [authenticatedUser, setAuthenticatedUser] = useState(null) const token = useStore(authToken) const navigate = useNavigate() const { pathname } = useLocation() function isTokenExpired() { // verify token expiration and return true or false } // Hook to check if user is logged in useEffect(() => { async function checkLogin () { if (token) { const expiredToken = isTokenExpired(token); if (expiredToken) { // clear out expired token and user from store and navigate to login page authToken.set(null) user.set(null) setIsLoggedIn(false); navigate("/login"); return; } } }; checkLogin() }, [pathname]) // Hook to fetch current user info and update state useEffect(() => { async function fetchUser() { const response = await axios.get("/api/auth/user", { headers: { 'Authorization': `Bearer ${token}` } }) if(response?.status !== 200) { throw new Error("Failed to fetch user data") } setAuthenticatedUser(response?.data) setIsLoggedIn(true) } fetchUser() }, []) const values = { isLoggedIn, authenticatedUser, setAuthenticatedUser } return({children} ) }
现在,第五步中创建的这两个 useEffect 挂钩负责管理整个应用程序的身份验证状态。每次您进行刷新时,它们都会运行以检查本地存储中的令牌,直接从后端检索最新的用户数据并更新您的“isLoggedIn”和“authenticatedUser”状态。您可以通过从 React 导入“AuthContext”和“useContext”钩子并在组件中调用它们来访问值并将它们用于某些条件渲染,从而在任何组件中使用状态。
import { useContext } from "react"; import { AuthContext } from "./AuthContext"; export default function MyLoggedInComponent() { const { isLoggedIn, authenticatedUser } = useContext(AuthContext) return( { isLoggedIn ?Welcome {authenticatedUser?.name}
: } > ) }
请记住,注销时,您必须通过将“authToken”和“user”存储设置为空来清除它们。您还需要将“isLoggedIn”设置为 false,将“authenticatedUser”设置为 null。
感谢您的阅读!
免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。
Copyright© 2022 湘ICP备2022001581号-3