You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 17:50:55