Firebase认证下React PrivateRoute失效求助:登录及权限路由异常
管理员登录系统PrivateRoute失效问题修复
问题核心原因
- PrivateRoute组件适配错误:react-router-dom v6 不再支持自定义Route组件,需改为包装式组件;且组件中使用的
isAuthenticated未在AuthContext中定义。 - AuthContext缺失必要属性:未提供认证状态判断的属性,需基于
currentUser推导认证状态。 - 登录逻辑冗余:AdminLogin中重复监听Auth状态,且未使用AuthContext封装的login方法。
- Dashboard状态监听重复:无需单独监听Auth状态,直接复用AuthContext中的全局状态即可。
分步修复代码
1. 修复AuthContext
添加isAuthenticated计算属性与全局加载状态,简化组件内的状态判断:
import React, { createContext, useContext, useState, useEffect } from "react"; import { auth } from "../firebase"; const AuthContext = createContext(); export const useAuth = () => useContext(AuthContext); export const AuthProvider = ({ children }) => { const [currentUser, setCurrentUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const unsubscribe = auth.onAuthStateChanged((user) => { setCurrentUser(user); setLoading(false); }); return unsubscribe; }, []); const login = (email, password) => { return auth.signInWithEmailAndPassword(email, password); }; const logout = () => { return auth.signOut(); }; return ( <AuthContext.Provider value={{ currentUser, isAuthenticated: !!currentUser, login, logout, loading }}> {!loading && children} </AuthContext.Provider> ); };
2. 重构PrivateRoute组件
改为v6兼容的包装式组件,直接对传入的子组件做权限校验:
import React from "react"; import { Navigate } from "react-router-dom"; import { useAuth } from "../context/AuthContext"; const PrivateRoute = ({ children }) => { const { isAuthenticated, loading } = useAuth(); if (loading) return <div>Loading...</div>; return isAuthenticated ? children : <Navigate to="/" replace />; }; export default PrivateRoute;
3. 修正App.js路由配置
使用重构后的PrivateRoute包裹受保护页面的element:
import React from "react"; import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; import AdminLogin from "./admin/AdminLogin"; import AdminDashboard from "./admin/AdminDashboard"; import MainSection from "./components/MainSection"; import CustomerForm from "./components/CustomerForm"; import { AuthProvider } from "./context/AuthContext"; import PrivateRoute from "./routes/PrivateRoute"; function App() { return ( <AuthProvider> <Router> <Routes> <Route path="/" element={<AdminLogin />} /> <Route path="/dashboard" element={ <PrivateRoute> <AdminDashboard /> </PrivateRoute> } /> <Route path="/client" element={ <PrivateRoute> <MainSection /> </PrivateRoute> } /> <Route path="/product" element={ <PrivateRoute> <CustomerForm /> </PrivateRoute> } /> </Routes> </Router> </AuthProvider> ); } export default App;
4. 简化AdminLogin组件
移除冗余的Auth状态监听,使用AuthContext封装的login方法:
import React, { useState } from "react"; import { useNavigate } from "react-router-dom"; import { useAuth } from "../context/AuthContext"; const AdminLogin = () => { const navigate = useNavigate(); const { login, isAuthenticated } = useAuth(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(null); if (isAuthenticated) { navigate("/dashboard"); return null; } const handleLogin = async (e) => { e.preventDefault(); setError(null); try { await login(email, password); navigate("/dashboard"); } catch (error) { setError(error.message); } }; return ( <div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8"> <div className="max-w-md w-full space-y-8"> <div> <h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900"> Admin Login </h2> </div> <form className="mt-8 space-y-6" onSubmit={handleLogin}> <input type="email" autoComplete="email" required className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm" placeholder="Email address" value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="password" autoComplete="current-password" required className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} /> <div className="flex items-center justify-between"> <div className="text-sm"> <p className="text-red-500">{error}</p> </div> </div> <div> <button type="submit" className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" > Sign in </button> </div> </form> </div> </div> ); }; export default AdminLogin;
5. 简化AdminDashboard组件
复用AuthContext的状态,移除重复的Auth监听:
import React from "react"; import { Link, useNavigate } from "react-router-dom"; import { useAuth } from "../context/AuthContext"; const AdminDashboard = () => { const navigate = useNavigate(); const { logout, isAuthenticated } = useAuth(); if (!isAuthenticated) { navigate("/"); return null; } const handleLogout = async () => { try { await logout(); navigate("/"); } catch (error) { console.error("Error signing out:", error); } }; return ( <div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8"> <div className="max-w-md w-full space-y-8"> <div> <h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900"> Welcome to Admin Dashboard </h2> </div> <div className="flex items-center justify-center"> <button onClick={handleLogout} className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" > Logout </button> </div> <div className="flex flex-col items-center justify-center mt-4 space-y-4"> <Link to="/client" className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" > Client </Link> <Link to="/product" className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" > Product </Link> </div> </div> </div> ); }; export default AdminDashboard;
修复后验证点
- 未登录时访问/dashboard、/client、/product会自动跳转到登录页
- 登录成功后自动跳转到Dashboard,且能正常访问Client和Product页面
- 登出后回到登录页,无法再访问受保护页面
内容的提问来源于stack exchange,提问作者Aahad
相关产品推荐
相关产品推荐

