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

MERN博客部署后添加评论报401 Unauthorized错误求助

MERN博客部署后添加评论接口401 Unauthorized问题

本地开发环境及Postman测试均正常,后端API部署在Render、前端部署在Vercel后,前端调用添加评论的POST接口时返回401 Unauthorized错误,无法正常添加评论,问题定位在认证环节。

相关代码

前端PostDetails组件代码

import { useNavigate, useParams } from "react-router-dom"
import Comment from "../components/Comment"
import Footer from "../components/Footer"
import Navbar from "../components/Navbar"
import {BiEdit} from 'react-icons/bi'
import {MdDelete} from 'react-icons/md'
import axios from "axios"
import { URL,IF } from "../url"
import { useContext, useEffect, useState } from "react"
import { UserContext } from "../context/UserContext"


const PostDetails = () => {

  const postId=useParams().id
  const [post,setPost]=useState({})
  const {user}=useContext(UserContext)
  const [comments,setComments]=useState([])
  const [comment,setComment]=useState("")
  const navigate=useNavigate()

  const fetchPost=async()=>{
    try{
      const res= await axios.get(URL+"/api/posts/"+postId)
      setPost(res.data)
    }
    catch(err){
      console.log(err)
    }
  }

  useEffect(()=>{
    fetchPost()
  },[postId])

  const handleDeletePost=async ()=>{
    try{
      const res=await axios.delete(URL+"/api/posts/"+postId,{withCredentials:true})
      console.log(res.data)
      navigate("/")
    }
    catch(err){
      console.log(err)
    }
  }

  const fetchPostComments=async()=>{
    try{
      const res=await axios.get(URL+"/api/comments/post/"+postId,{withCredentials:true})
      setComments(res.data)
    }
    catch(err){
      console.log(err)
    }
  }

  useEffect(()=>{
    fetchPostComments()
  },[postId])

  const postComment=async(e)=>{
    e.preventDefault()
    try{
      const res=await axios.post(URL+"/api/comments/create",
      {comment:comment,author:user.username,postId:postId,userId:user._id},
      {withCredentials:true})
      console.log(res.data)
      fetchPostComments()
       setComment("")
       window.location.reload(true)
    }
    catch(err){
         console.log(err)
    }
  }
  
  return (
    <div>
        <Navbar/>
        <br/>
        <br/>
        <br/>
        <div className="px-8 md:px-[200px] mt-8">
            <div className="flex items-center justify-between">
                <h1 className="text-2xl font-bold text-black md:text-3xl">{post.title}</h1>
                <div className="flex items-center justify-center space-x-2">
                    <p className="cursor-pointer" onClick={()=>navigate("/edit/"+postId)}><BiEdit/></p>
                    <p className="cursor-pointer" onClick={handleDeletePost}><MdDelete/></p>
                </div>
            </div>
            <div className="flex items-center justify-between mt-2 md:mt-4">
            <p>@{post.username}</p>
            <div className="flex space-x-2">
                <p>{new Date(post.updatedAt).toString().slice(0,10)}</p>
                <p>{new Date(post.updatedAt).toString().slice(16,24)}</p>
            </div>
            </div>
            <img src={IF+post.photo} className="w-full mx-auto mt-8" alt=""/>
            <p className="mx-auto mt-8">{post.desc}</p>
            <div className="flex items-center mt-8 space-x-4 font-semibold">
                <p>Categories:</p>
                <div className="flex items-center justify-center space-x-2">
                    {post.categories?.map((c,i)=>(
                 <>
                <div key={i} className="px-3 py-1 bg-gray-300 rounded-lg">{c}</div>
                 </>
            
                   ))}
            
                 </div>
             </div>
                 <div className="flex flex-col mt-4">
                    <h3 className="mt-6 mb-4 font-semibold">Comments:</h3>
                      {comments?.map((c)=>(
                        <Comment key={c._id} c={c} post={post} />
                    ))}
                 </div>
                       <div className="flex flex-col mt-4 md:flex-row">
                         <input onChange={(e)=>setComment(e.target.value)} type="text" placeholder="write a comment" className="md:w-[90%] outline-none px-4 mt-4 md:mt-0"/>
                         <button onClick={postComment} className="bg-black text-sm text-white px-2 py-2 md:w-[20%] mt-4 md:mt-0">Add Comment</button>
                         <br/>
                       </div>
            </div>
        <Footer/>
    </div>
  )
}

export default PostDetails

后端评论接口代码

const express=require('express')
const router = express.Router();
const Comment=require('../models/Comment');
const verifyToken = require('../verifyToken');

//CREATE
router.post("/create",verifyToken,async(req,res)=>{
  try{
     const newComment=new Comment(req.body)
     const savedComment=await newComment.save()
     res.status(200).json(savedComment)
  }
  catch(err){
    res.status(200).json(err)
  }
})

//UPDATE
router.put("/:id",verifyToken,async (req,res)=>{
    try{
        const updatedComment=await Comment.findByIdAndUpdate(req.params.id,{$set:req.body},{new:true})
        res.status(200).json(updatedComment)
    }
    catch(err){
        res.status(500).json(err)
    }
})

//DELETE
router.delete("/:id",verifyToken,async (req,res)=>{
    try{
        await Comment.findByIdAndDelete(req.params.id)
         res.status(200).json("Comment has been deleted!")
    }
    catch(err){
        res.status(500).json(err)
    }
})

//GET POST COMMENT
router.get("/post/:postId",async (req,res)=>{
    try{
        const comments=await Comment.find({postId:req.params.postId})
        res.status(200).json(comments)
    }
    catch(err){
        res.status(500).json(err)
    }
})

module.exports=router

Comment模型代码

const mongoose=require('mongoose')

const CommentSchema=new mongoose.Schema({
    comment:{
        type:String,
        required:true,
    },
    author:{
        type:String,
        required:true,
    },
    postId:{
        type:String,
        required:true,
    },
    userId:{
        type:String,
        required:true
    }
},{timestamps:true})

module.exports=mongoose.model("Comment",CommentSchema)

服务器主文件代码

const express=require('express')
const app=express()
const mongoose=require('mongoose')
const dotenv=require('dotenv')
const cors=require('cors')
const path=require("path")
const multer=require('multer')
const cookieParser=require('cookie-parser')
const authRoute=require('./routes/auth')
const userRoute=require('./routes/users')
const postRoute=require('./routes/posts')
const commentRoute = require("./routes/comment")
const ottRoute=require('./routes/ott')
const releaseRoute=require('./routes/release')
const responseRoute=require('./routes/response');
const ratingRoute=require('./routes/rating')

//database
const connectDB=async()=>{
    try{
        await mongoose.connect(process.env.MONGO_URL)
        console.log("database is connected successfully!")
    }
    catch(err){
        console.log(err)
    }
}

//middlewares
dotenv.config()
app.use(express.json())
app.use("/images",express.static(path.join(__dirname,"/images")))
app.use(cors({ origin: "http://localhost:5173", credentials: true }));
app.use(cookieParser())
app.use("/api/auth",authRoute)
app.use("/api/users",userRoute)
app.use("/api/posts",postRoute)
app.use("/api/comments", commentRoute)
app.use("/api/otts",ottRoute);
app.use("/api/releases",releaseRoute)
app.use("/api/responses",responseRoute)
app.use("/api/rating",ratingRoute)

//image upload
const storage=multer.diskStorage({
    destination:(req,file,fn)=>{
        fn(null,"images")
    },
    filename:(req,file,fn)=>{
        fn(null,req.body.img)
    }
})

const upload=multer({storage:storage})
app.post("/api/upload",upload.single("file"),(req,res)=>{
    res.status(200).json("Image has been uploaded successfully!")
})

app.listen(process.env.PORT,()=>{
    connectDB()
    console.log("app is running on port "+process.env.PORT)
})

verifyToken中间件代码

const jwt=require('jsonwebtoken')

const verifyToken=(req,res,next)=>{
    const token=req.cookies.token
    console.log(token)
    if(!token){
        return res.status(401).json("You are not authenticated!")
    }
    jwt.verify(token,process.env.SECRET,async (err,data)=>{
        if(err){
            return res.status(403).json("Token is not valid!")
        }
        req.userId=data._id
        console.log("passed")
        next()
    })
}

module.exports=verifyToken

问题修复方案

1. 修正CORS配置

后端当前CORS只允许本地域名,需要添加Vercel前端域名:

// 服务器主文件中替换原CORS配置
const allowedOrigins = [
  "http://localhost:5173",
  "https://your-vercel-domain.vercel.app" // 替换为你的实际Vercel前端域名
];

app.use(cors({
  origin: (origin, callback) => {
    // 允许无origin的请求(如Postman)
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error("Not allowed by CORS"));
    }
  },
  credentials: true
}));

或者使用环境变量动态配置(推荐生产环境):

// 在Render控制台添加环境变量FRONTEND_URL,值为你的Vercel域名
app.use(cors({
  origin: process.env.FRONTEND_URL,
  credentials: true
}));

2. 完善Cookie跨域属性

登录接口设置Cookie时,需添加sameSite: 'None'和secure: true(线上HTTPS环境必须):

// 找到登录接口中设置token的代码,修改为:
res.cookie("token", token, {
  httpOnly: true,
  secure: process.env.NODE_ENV === "production", // 生产环境启用HTTPS安全属性
  sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
  maxAge: 7 * 24 * 60 * 60 * 1000 // 7天有效期
});

3. 优化Axios全局配置

前端全局开启withCredentials,避免每个请求重复设置:

// 在前端入口文件(如src/main.jsx)添加
import axios from 'axios';
import { URL } from './url';

axios.defaults.baseURL = URL;
axios.defaults.withCredentials = true;

4. 日志排查验证

在verifyToken中间件添加详细日志,查看Render控制台日志确认token是否正常传递:

const verifyToken=(req,res,next)=>{
    const token=req.cookies.token
    console.log("[VerifyToken] Received token:", token);
    if(!token){
        console.log("[VerifyToken] No token found in cookies");
        return res.status(401).json("You are not authenticated!")
    }
    jwt.verify(token,process.env.SECRET,(err,data)=>{
        if(err){
            console.log("[VerifyToken] Token verification failed:", err.message);
            return res.status(403).json("Token is not valid!")
        }
        req.userId=data._id
        console.log("[VerifyToken] Authenticated user ID:", req.userId);
        next()
    })
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 13:10:54