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

NodeJS认证系统跳转请求无法携带JWT Token的问题咨询

NodeJS JWT认证系统:后续请求携带Token问题

问题背景

搭建了一套NodeJS认证系统,核心逻辑如下:

  • /auth/register 处理用户注册
  • /auth/login 验证用户身份后生成JWT Token并返回
  • 除上述两个路由及视图渲染路由外,其余所有路由需通过authMiddleware中间件校验:检查请求头是否包含带Token的Authorization字段,存在则放行,否则抛出401错误

目前遇到的问题:登录视图提交表单后,成功调用/auth/login获取Token并存入localStorage,但跳转至/home页面时,请求头未携带Token,触发401错误。不清楚如何在后续请求中传递Token,也不确定当前用法是否正确。

相关代码

控制器代码

const AuthenticationError = require("../errors/authError");
const jwt = require("jsonwebtoken");
const User = require("../models/user");

module.exports = {
  renderRegisterPage: (req, res, next) => {
    res.render("register");
  },

  register: async ({ body: { username, password, email } }, res, next) => {
    try {
      const newUser = new User({ username, password, email });
      await newUser.save();
      res.status(200).end();
    } catch (error) {
      next(error);
    }
  },

  renderLoginPage: (req, res, next) => {
    res.render("login");
  },

  login: async (req, res, next) => {
    const { username, password } = req.body;
    try {
      const user = await User.findOne({ username });
      if (!user)
        throw new AuthenticationError("User not found : invalid username", 404);

      const passwordMatch = await user.comparePassword(password);
      if (!passwordMatch)
        throw new AuthenticationError("Incorrect password", 401);

      const token = jwt.sign({ userId: user.id }, process.env.SECRET_KEY, {
        expiresIn: "1h",
      });
      res.status(200).json({ token });
    } catch (error) {
      next(error);
    }
  },
};

authMiddleware代码

const jwt = require("jsonwebtoken");
const User = require("../models/user");
const AuthenticationError = require("../errors/authError");

module.exports = async (req, res, next) => {
  const token = req.headers.authorization?.split(" ")[1];
  try {
    console.log(token);
    if (!token) throw new AuthenticationError("Authentication required", 401);
    const decodedToken = jwt.verify(token, process.env.SECRET_KEY);
    const user = await User.findById(decodedToken.userId);
    if (!user) throw new AuthenticationError("User not found", 404);
    req.user = user;
    next();
  } catch (error) {
    next(error);
  }
};

登录视图代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="/styles/auth.css" />
    <title>Login</title>
  </head>
  <body>
    <h1>Login</h1>

    <form id="loginForm">
      <label for="username">Username:</label>
      <input type="text" id="username" name="username" required />

      <label for="password">Password:</label>
      <input type="password" id="password" name="password" required />

      <button type="submit">Login</button>
    </form>

    <script>
      document
        .getElementById("loginForm")
        .addEventListener("submit", function (event) {
          event.preventDefault();

          var formData = {
            username: document.getElementById("username").value,
            password: document.getElementById("password").value,
          };

          fetch("http://localhost:6543/auth/login", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify(formData),
          })
            .then((response) => response.json())
            .then(({ token }) => {
              localStorage.setItem("token", token);
              window.location.href = "/home";
            })
            .catch((error) => {
              console.log("Error:", error);
            });
        });
    </script>
  </body>
</html>

解决方案

1. 区分页面请求与API请求的Token携带逻辑

window.location.href = "/home"是浏览器发起的页面渲染请求,这类请求不会自动携带localStorage中的Token。而前端发起的AJAX/fetch请求属于API请求,需要手动配置请求头。

2. 处理页面渲染请求的认证(如/home页面)

如果/home是服务器渲染的页面,需要让浏览器在页面请求时自动携带Token,最常用的方式是将Token存入Cookie:

修改登录视图的跳转逻辑

在存入localStorage的同时,把Token写入Cookie:

.then(({ token }) => {
  localStorage.setItem("token", token);
  // 将Token写入Cookie,浏览器会自动在页面请求时携带
  document.cookie = `token=${token}; path=/; max-age=3600`;
  window.location.href = "/home";
})

修改authMiddleware支持从Cookie取Token

更新中间件代码,优先从Authorization头取Token,其次从Cookie取:

const token = req.headers.authorization?.split(" ")[1] || req.cookies.token;

注意:若使用Cookie,建议根据场景添加安全属性,比如secure(HTTPS环境下)、sameSite=Strict防止CSRF,若不需要前端读取Token可添加httpOnly。

3. 处理API请求的Token携带

对于前端发起的API请求,封装一个通用的fetch函数,自动带上Token:

// 封装带认证的fetch
function authenticatedFetch(url, options = {}) {
  const token = localStorage.getItem("token");
  const headers = {
    ...options.headers,
    // 若存在Token,添加Authorization头(Bearer格式)
    ...(token ? { Authorization: `Bearer ${token}` } : {})
  };
  return fetch(url, { ...options, headers });
}

之后所有需要认证的API请求都使用这个函数,比如:

// 示例:获取用户信息
authenticatedFetch("/api/user/profile")
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

4. 当前代码的正确性验证

  • 控制器与authMiddleware的核心逻辑是正确的:JWT生成、Token解析校验、用户关联流程均符合规范。
  • 登录视图的问题仅在于未处理后续请求的Token携带逻辑,补充上述代码即可解决401问题。

内容的提问来源于stack exchange,提问作者Maxime Arnould

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 15:24:51