如何在React+React Router应用中基于JWT实现路由认证权限控制?
Hey there! Let's get that mock auth swapped out for a real JWT-based system in your React + React Router app with Express. I'll break this down into clear, actionable steps so you can implement it without confusion.
First, let's handle the JWT generation and validation on your Express server.
Install Dependencies
Start by installing the required packages:
npm install jsonwebtoken bcryptjs cors
jsonwebtoken: For creating and verifying JWT tokensbcryptjs: For securely hashing and comparing user passwordscors: To handle cross-origin requests between your React frontend and Express backend
User Login Endpoint
Create a login route that validates user credentials and returns a JWT token:
const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const cors = require('cors'); const User = require('./models/User'); // Assume you have a Mongoose/Sequelize User model // Configure CORS (adjust origin to match your React app's URL) app.use(cors({ origin: 'http://localhost:3000', credentials: true })); // Login route app.post('/api/login', async (req, res) => { try { const { email, password } = req.body; const user = await User.findOne({ email }); // Check if user exists if (!user) { return res.status(401).json({ message: 'Invalid email or password' }); } // Verify password const isPasswordValid = await bcrypt.compare(password, user.password); if (!isPasswordValid) { return res.status(401).json({ message: 'Invalid email or password' }); } // Generate JWT token (expires in 1 hour for security) const token = jwt.sign( { userId: user._id, email: user.email }, process.env.JWT_SECRET, // Store this in a .env file, NEVER hardcode! { expiresIn: '1h' } ); res.json({ token, user: { id: user._id, email: user.email } }); } catch (err) { console.error(err); res.status(500).json({ message: 'Server error occurred' }); } });
JWT Validation Middleware
Create a middleware to protect your authenticated API routes:
const authenticateToken = (req, res, next) => { const authHeader = req.headers['authorization']; // Token is sent in the format: "Bearer <token>" const token = authHeader && authHeader.split(' ')[1]; if (!token) { return res.status(401).json({ message: 'No authentication token provided' }); } jwt.verify(token, process.env.JWT_SECRET, (err, user) => { if (err) { return res.status(403).json({ message: 'Invalid or expired token' }); } // Attach user data to the request for use in protected routes req.user = user; next(); }); }; // Example protected route app.get('/api/user/profile', authenticateToken, (req, res) => { res.json({ user: req.user, profileData: 'Your protected profile data' }); });
Now let's implement the auth logic in your React app to protect routes and manage user state.
Install Dependencies
npm install react-router-dom axios
Create an Auth Context
This will manage global user state, login/logout functions, and token persistence:
import { createContext, useContext, useState, useEffect } from 'react'; import axios from 'axios'; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); // Check for stored token on app load useEffect(() => { const token = localStorage.getItem('jwtToken'); if (token) { // Verify token is still valid by hitting a protected endpoint axios.get('/api/user/profile', { headers: { Authorization: `Bearer ${token}` } }) .then(res => setUser(res.data.user)) .catch(() => localStorage.removeItem('jwtToken')) // Clear invalid token .finally(() => setLoading(false)); } else { setLoading(false); } }, []); // Login function const login = async (email, password) => { try { const res = await axios.post('/api/login', { email, password }); const { token, user } = res.data; localStorage.setItem('jwtToken', token); setUser(user); return { success: true }; } catch (err) { return { success: false, message: err.response.data.message }; } }; // Logout function const logout = () => { localStorage.removeItem('jwtToken'); setUser(null); }; return ( <AuthContext.Provider value={{ user, loading, login, logout }}> {children} </AuthContext.Provider> ); }; // Custom hook to access auth context export const useAuth = () => useContext(AuthContext);
Create a Protected Route Component
This component will guard routes that require authentication:
import { Navigate, Outlet } from 'react-router-dom'; import { useAuth } from './AuthContext'; export const ProtectedRoute = () => { const { user, loading } = useAuth(); // Show loading state while verifying token if (loading) { return <div className="loading">Loading...</div>; // Replace with your loading UI } // Redirect to login if user isn't authenticated return user ? <Outlet /> : <Navigate to="/login" replace />; };
Configure Your Routes
Wrap your app with the AuthProvider and use ProtectedRoute for restricted pages:
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import { AuthProvider } from './AuthContext'; import { ProtectedRoute } from './ProtectedRoute'; import Login from './pages/Login'; import Dashboard from './pages/Dashboard'; import Profile from './pages/Profile'; import Home from './pages/Home'; function App() { return ( <AuthProvider> <Router> <Routes> {/* Public routes */} <Route path="/" element={<Home />} /> <Route path="/login" element={<Login />} /> {/* Protected routes (only accessible if authenticated) */} <Route element={<ProtectedRoute />}> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/profile" element={<Profile />} /> </Route> {/* Catch-all route for 404 */} <Route path="*" element={<Navigate to="/" replace />} /> </Routes> </Router> </AuthProvider> ); } export default App;
Example Login Page
Implement a login form that uses the useAuth hook:
import { useState } from 'react'; import { useAuth } from '../AuthContext'; import { useNavigate } from 'react-router-dom'; const Login = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const { login } = useAuth(); const navigate = useNavigate(); const handleSubmit = async (e) => { e.preventDefault(); setError(''); const result = await login(email, password); if (result.success) { navigate('/dashboard'); // Redirect to protected page on success } else { setError(result.message); } }; return ( <div className="login-container"> <h2>Login</h2> {error && <p className="error-message">{error}</p>} <form onSubmit={handleSubmit}> <div className="form-group"> <label>Email:</label> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required /> </div> <div className="form-group"> <label>Password:</label> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} required /> </div> <button type="submit" className="login-btn">Login</button> </form> </div> ); }; export default Login;
- Never hardcode your JWT secret: Store it in a
.envfile and usedotenvin Express to access it. - Set short token expiration times: 1 hour is standard. Add a refresh token flow if you need longer sessions.
- Handle token expiration: In your frontend, catch 403 errors from API calls, clear the token, and redirect to login.
- Be mindful of XSS risks: Storing JWT in
localStorageis common but vulnerable to XSS attacks. If security is critical, consider using HTTP-only cookies instead (requires backend adjustments).
内容的提问来源于stack exchange,提问作者vidu.sh

