React-Router-DOM useParams无法获取含.的完整JWT激活令牌
问题:React Router无法获取含"."的完整JWT激活令牌
我通过邮件发送包含JWT激活令牌的认证URL:http://localhost:5173/activation/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NWZkNmM0OTMyOWI4YzhjNjFjMjJiY2YiLCJpYXQiOjE3MTExMDcxNDUsImV4cCI6MTcxMTE5MzU0NX0.BLkEkRtRxYk3uKKobWRuD0rsf7qob35l5-OmdJfRqyU。使用React-Router-DOM的useParams()获取"activation/"后的令牌时,若令牌不含"."字符可正常获取(如eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9),但含"."的完整JWT令牌只能拿到第一个"."之前的部分,无法获取完整值。
后端代码
routes/auth.js
const express = require("express"); const authController = require("../controllers/auth"); const router = express.Router(); router.post("/signup", authController.postSignup); router.post("/activation", authController.activateAccount); module.exports = router;
controllers/auth.js
const bcrypt = require("bcrypt"); const User = require("../models/user"); const { v4: uuidv4 } = require("uuid"); const jwt = require("jsonwebtoken"); const { getAuth } = require("firebase-admin/auth"); const emailRegex = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/; // regex for email const passwordRegex = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20}$/; // regex for password const sendMail = require("../utils/sendMail"); const generateUserName = async (email) => { let username = email.split("@")[0]; const isUsernameExists = await User.exists({ "personal_info.username": username }); if (isUsernameExists) { username += uuidv4().substring(0, 5); } return username; }; const fortmatDataToSend = (user) => { const access_token = jwt.sign( { id: user._id }, process.env.SECRET_ACCESS_KEY, { expiresIn: "1h" } ); return { access_token, _id: user._id, profile_img: user.personal_info.profile_img, username: user.personal_info.username, fullname: user.personal_info.fullname }; }; exports.postSignup = async (req, res) => { const { fullname, email, password } = req.body; try { if (fullname.length < 3) { return res .status(403) .json({ error: "Fullname must be at least 3 letters long !" }); } if (!email.length) { return res.status(403).json({ error: "Enter Email" }); } if (!emailRegex.test(email)) { return res.status(403).json({ error: "Invalid email address" }); } if (!passwordRegex.test(password)) { return res.status(403).json({ error: "Password should be 6 to 20 characters long with a numeric, 1 lowercase and 1 uppercase letters!" }); } const existingUser = await User.findOne({ email: email }).exec(); if (existingUser) { return res.status(409).json({ error: "Email is already registered" }); } const hashedPassword = await bcrypt.hash(password, 12); const username = await generateUserName(email); const user = new User({ personal_info: { fullname, email, password: hashedPassword, username, active: false } }); const activation_token = jwt.sign( { userId: user._id }, process.env.SECRET_ACCESS_KEY, { expiresIn: "1d" } ); const activationLink = `${process.env.CLIENT}/activation/${activation_token}`; await sendMail({ email: email, subject: "Activate Your Account", message: ` <p>Hello ${fullname} 👋, Please click following link to active your account ⬇️</p> <a href="${activationLink}">Verify Your Email</a> ` }); await user.save(); return res.status(200).json({ status: "Registration successful. Please check your email for activation link." }); } catch (err) { if (err.code === 11000) { return res.status(409).json({ error: "Email is already registered!" }); } return res.status(500).json({ error: err.message }); } }; exports.activateAccount = async (req, res) => { try { const { activation_token } = req.body; const decodedToken = jwt.verify( activation_token, process.env.SECRET_ACCESS_KEY ); const userId = decodedToken.userId; const user = await User.findById(userId); if (!user) { return res.status(404).json({ error: "User not found" }); } if (user.personal_info.active) { return res.status(400).json({ error: "Account already activated" }); } user.personal_info.active = true; await user.save(); return res.status(200).json({ message: "Account activated successfully" }); } catch (error) { if (error.name === "TokenExpiredError") { return res.status(400).json({ error: "Activation token expired" }); } else if (error.name === "JsonWebTokenError") { return res.status(400).json({ error: "Invalid activation token" }); } return res.status(500).json({ error: "Internal server error" }); } };
前端代码
App.js
import { Route, Routes } from "react-router-dom"; import Navbar from "./components/navbar.component"; import UserAuthForm from "./pages/userAuthForm"; import UserContextProvider from "../context/user-context"; import Editor from "./pages/editor"; import Activation from "./pages/Activation"; const App = () => { return ( <UserContextProvider> <Routes> <Route path="/" element={<Navbar />}> <Route path="signin" element={<UserAuthForm type="signin" />}></Route> <Route path="signup" element={<UserAuthForm type="signup" />}></Route> </Route> <Route path="/editor" element={<Editor />} /> <Route path="/activation/:activation_token" element={<Activation />} /> </Routes> </UserContextProvider> ); }; export default App;
Activation.jsx
import axios from "axios"; import { useEffect, useState } from "react"; import { useLocation, useParams } from "react-router-dom"; const Activation = () => { const { activation_token } = useParams(); const [error, setError] = useState(false); const [loading, setLoading] = useState(true); useEffect(() => { if (activation_token) { const activateAccount = async () => { try { await axios.post( `http://localhost:3000/activation/${activation_token}` ); } catch (error) { setError(true); setLoading(false); } }; activateAccount(); } }, [activation_token]); return ( <div> {loading ? ( <p>Loading...</p> ) : error ? ( <div> <h1>Activation Error</h1> <p> There was an error activating your account. Please try again later. </p> </div> ) : ( <div> <h1>Account Activated Successfully</h1> <p>Your account has been successfully activated.</p> </div> )} </div> ); }; export default Activation;
问题原因及解决方法
1. React Router参数匹配规则问题
React Router默认会把URL中的.识别为文件扩展名分隔符,参数匹配会在第一个.处截断。要让参数包含.,需要修改路由的匹配规则:
修改App.js中的激活路由,二选一即可:
// 方法1:使用通配符(*),匹配路径结束前的所有字符 <Route path="/activation/:activation_token(*)" element={<Activation />} /> // 方法2:使用正则匹配所有非/的字符(包含.) <Route path="/activation/:activation_token([^/]+)" element={<Activation />} />
2. 前端请求后端的方式错误
后端activateAccount接口是从请求体中获取activation_token,但前端代码把令牌放在了URL路径中,导致后端无法正确接收参数。修改Activation.jsx中的请求代码:
await axios.post( "http://localhost:3000/activation", { activation_token: activation_token } // 将令牌放在请求体中 );
完成以上修改后,就能正确获取完整JWT令牌,后端也能正常处理激活请求。
内容的提问来源于stack exchange,提问作者Tran Phat Trien
相关产品推荐
相关产品推荐

