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

登录后JWT获取与验证异常:发布帖子提示jwt must be provided

问题:发布帖子时抛出「jwt must be provided」错误

我是编程初学者,正在用React JS、Node.js Express和MySQL开发一个支持注册、登录、发帖子的社交网络。注册和登录功能正常,但发布帖子时出现以下错误:

C:\Users\sever\code\REACT\2022-03-JS-Nantes-Dear\backend\node_modules\jsonwebtoken\verify.js:53    
    return done(new JsonWebTokenError('jwt must be provided'));
                ^
JsonWebTokenError: jwt must be provided

相关后端代码

PostController.js

const Joi = require("joi");
const dayjs = require("dayjs");
const jwt = require("jsonwebtoken");
const models = require("../models");

class PostController {
  static add = async (req, res) => {
    const { content, categoryId } = req.body;
    const { accessToken } = req.body;
    const user = jwt.verify(accessToken, process.env.JWT_AUTH_SECRET);

    const validationErrors = Joi.object({
      content: Joi.string().max(255).required(),
      categoryId: Joi.number().required(),
    }).validate({ content, categoryId }).error;

    if (validationErrors) {
      res.status(422).send(validationErrors);
      return;
    }

    models.post
      .insert({
        content,
        userId: user.id,
        categoryId: parseInt(categoryId, 10),
        createdAt: dayjs().format("YYYY-MM-DD HH:mm:ss"),
        likers: 0,
        signals: 0,
      })
      .then(([result]) => {
        res.status(201).send({
          ...result,
          id: result.insertId,
        });
      })
      .catch((err) => {
        console.error(err);
        res.status(500).send({
          error: err.message,
        });
      });
  };
}

module.exports = PostController;

PostManager.js

const AbstractManager = require("./AbstractManager");

class PostManager extends AbstractManager {
  static table = "post";

  insert(post) {
    return this.connection.query(
      `insert into ${PostManager.table} (content, userId, categoryId, createdAt, likers, signals) values (?, ?, ?, ?, ?, ?)`,
      [
        post.content,
        post.userId,
        post.categoryId,
        post.createdAt,
        post.likers,
        post.signals,
      ]
    );
  }
}

module.exports = PostManager;

post.routes.js

const express = require("express"); 
const { PostController } = require("../controllers");

const routes = express.Router();

routes.get("/post", PostController.browse); 
routes.get("/post/:id", PostController.read); 
routes.put("/post/:id", PostController.edit); 
routes.post("/post/posttext", PostController.add); 
routes.delete("/post/:id", PostController.delete);

module.exports = routes;

前端相关代码(PostText组件)

import { React, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import "./styles/PostText.css";
import axios from "axios";
import swal from "sweetalert";
import ButtonReturnGreen from "./ButtonReturnGreen";
import ButtonPublierPost from "./ButtonPublierPost";

function PostText() {
  const [content, setContent] = useState("");
  const [categoryId, setCategoryId] = useState("");
  const [categories, setCategories] = useState([]);

  const navigate = useNavigate();

  useEffect(() => {
    axios
      .get(`${import.meta.env.VITE_BACKEND_URL}/category`)
      .then((res) => setCategories(res.data))
      .catch((err) => {
        console.error(err);
      });
  }, []);

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!content || !categoryId) {
      swal({
        title: "Error!",
        text: "Merci de spécifier la categorie et de publier un post",
        icon: "error",
        confirmButtonText: "parfait",
      });
    } else {
      axios
        .post(
          `${import.meta.env.VITE_BACKEND_URL}/post/posttext`,
          { content, categoryId },
          { withCredentials: true }
        )
        .then(() => navigate("/home", { replace: true }))
        .catch((err) => {
          console.warn(err);
        });
    }
  };

  return (
    <div className="div-post-texte">
      <div className="title-post-category">
        <ButtonReturnGreen />
        <h1>À quelle catégorie</h1>
        <h1>appartient ce post?</h1>
      </div>
      <div className="form-post-text">
        <select
          className="option-category-post"
          name="category"
          id="share-select"
          value={categoryId}
          onChange={(e) => setCategoryId(e.target.value)}
        >
          <option value="0">--Choisir une catégorie--</option>
          {categories.map((category) => (
            <option key={category.id} value={category.id}>
              {category.name}
            </option>
          ))}
        </select>
      </div>
      <div className="bloc-texte-post">
        <h1 className="title-post-message"> Écrit ton message ici :</h1>
        <form className="form-post">
          <label className="postform" htmlFor="post">
            <textarea
              className="inputformpost"
              type="content"
              name="content"
              id="content"
              placeholder="Partagez un message, une question, une expérience, un doute... avec la communauté! La sexualité n'est plus un tabou! Ta publication est anonyme."
              value={content}
              onChange={(e) => setContent(e.target.value)}
            />
          </label>
        </form>
      </div>
      <div className="button-publier-post">
        <ButtonPublierPost handleSubmit={handleSubmit} />
      </div>
    </div>
  );
}
export default PostText;

问题原因与解决方案

原因

后端从请求body里获取accessToken,但前端发布帖子时根本没传递这个token,导致jwt.verify因缺少token抛出错误。此外,直接在请求体里传token不符合REST规范,标准做法是通过Authorization请求头传递。

解决方案

1. 前端:存储并传递JWT Token

  • 登录成功后,把后端返回的accessToken存入localStorage(或React Context/Redux,根据项目规模选择):
    // 登录接口成功回调示例
    axios.post('/login', { email, password })
      .then(res => {
        localStorage.setItem('accessToken', res.data.accessToken);
        navigate('/home');
      })
    
  • 修改发布帖子的请求,在请求头里带上token:
    const handleSubmit = (e) => {
      e.preventDefault();
      if (!content || !categoryId) {
        swal({
          title: "Error!",
          text: "Merci de spécifier la categorie et de publier un post",
          icon: "error",
          confirmButtonText: "parfait",
        });
      } else {
        // 从localStorage获取token
        const accessToken = localStorage.getItem('accessToken');
        axios
          .post(
            `${import.meta.env.VITE_BACKEND_URL}/post/posttext`,
            { content, categoryId },
            {
              withCredentials: true,
              headers: {
                // 标准Bearer认证格式
                Authorization: `Bearer ${accessToken}`
              }
            }
          )
          .then(() => navigate("/home", { replace: true }))
          .catch((err) => {
            console.warn(err);
          });
      }
    };
    

2. 后端:用中间件统一验证JWT

  • 创建JWT验证中间件(middleware/auth.js):
    const jwt = require('jsonwebtoken');
    
    module.exports = (req, res, next) => {
      try {
        // 从请求头提取token,分割"Bearer "前缀
        const token = req.headers.authorization.split(' ')[1];
        const decodedUser = jwt.verify(token, process.env.JWT_AUTH_SECRET);
        // 将解析后的用户信息挂载到req对象,供后续控制器使用
        req.user = decodedUser;
        next();
      } catch (error) {
        return res.status(401).json({ error: '无效或缺失的身份验证token' });
      }
    };
    
  • 修改路由,给发布帖子接口添加中间件保护:
    const express = require("express"); 
    const { PostController } = require("../controllers");
    const authMiddleware = require('../middleware/auth'); // 引入中间件
    
    const routes = express.Router();
    
    routes.get("/post", PostController.browse); 
    routes.get("/post/:id", PostController.read); 
    routes.put("/post/:id", PostController.edit); 
    // 添加中间件,验证通过才允许访问add方法
    routes.post("/post/posttext", authMiddleware, PostController.add); 
    routes.delete("/post/:id", PostController.delete);
    
    module.exports = routes;
    
  • 修改PostController,移除从body取token的代码,直接使用中间件解析后的用户信息:
    static add = async (req, res) => {
      const { content, categoryId } = req.body;
      // 删掉以下两行:
      // const { accessToken } = req.body;
      // const user = jwt.verify(accessToken, process.env.JWT_AUTH_SECRET);
      // 直接用中间件挂载的req.user
      const user = req.user;
    
      // 后续验证和插入逻辑不变
      const validationErrors = Joi.object({
        content: Joi.string().max(255).required(),
        categoryId: Joi.number().required(),
      }).validate({ content, categoryId }).error;
    
      if (validationErrors) {
        res.status(422).send(validationErrors);
        return;
      }
    
      models.post
        .insert({
          content,
          userId: user.id,
          categoryId: parseInt(categoryId, 10),
          createdAt: dayjs().format("YYYY-MM-DD HH:mm:ss"),
          likers: 0,
          signals: 0,
        })
        .then(([result]) => {
          res.status(201).send({
            ...result,
            id: result.insertId,
          });
        })
        .catch((err) => {
          console.error(err);
          res.status(500).send({
            error: err.message,
          });
        });
    };
    

这样修改后,前端会正确传递token,后端通过中间件统一验证身份,就能解决「jwt must be provided」的错误,同时代码更符合规范、易于维护。

内容的提问来源于stack exchange,提问作者Séverine

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 22:30:58