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

Express服务非/user路由出现CORS Missing Allow Credentials错误求助

跨域错误(CORS Missing Allow Credentials)排查与解决

问题场景

除/user路由外,所有挂载authMiddleware的路由均触发CORS Missing Allow Credentials错误;请求/expense/expenses时还会出现502 Bad Gateway错误,浏览器引用策略为strict-origin-when-cross-origin。

现有配置代码

Express服务主文件

import express from "express";
import bodyParser from "body-parser";
import mongoose from "mongoose";
import cors from "cors";
import dotenv from "dotenv";
import helmet from "helmet";
import morgan from "morgan";
import cookieParser from "cookie-parser";
import categoryRoutes from "./routes/category.js";
import subCategoryRoutes from "./routes/subCategory.js";
import expenseRoutes from "./routes/expense.js";
import userRoutes from "./routes/user.js";
import { authMiddleware } from "./middleware/authMiddleware.js";

dotenv.config();

const app = express();
app.use(cookieParser());
app.use(express.json());
app.use(helmet());
app.use(helmet.crossOriginResourcePolicy({ policy: "cross-origin" }));

app.use(morgan("common"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(
  cors({
    origin: *********************,
    credentials: true,
  })
);

app.use("/category", authMiddleware, categoryRoutes);
app.use("/subCategory", authMiddleware, subCategoryRoutes);
app.use("/expense", authMiddleware, expenseRoutes);
app.use("/user", userRoutes);

const PORT = process.env.PORT || 9000;

mongoose
  .connect(process.env.MONGO_URL, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then(async () => {
    app.listen(PORT, () => {
      console.log(`Server running on port: ${PORT}`);
    });
  })
  .catch((error) => {
    console.log(`${error} connecting to DB`);
  });

请求头信息

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Cookie: jwt=*************************
Host: *************************
Origin: https://*********************
Referer: https:/**********************/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
TE: trailers
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0

authMiddleware中间件代码

import jwt from "jsonwebtoken";

export const authMiddleware = async (req, res, next) => {
  const { id, token } = req.cookies;
  console.log(id, token);
  try {
    if (id) {
      req.userId = id;
    res.setHeader(
      "Access-Control-Allow-Origin",
      "**************************"
    );
    res.setHeader("Access-Control-Allow-Credentials", true);
      next();
    }

    if (token) {
      const decoded = jwt.verify(token, process.env.JWT_SECRET);
      if (decoded) {
        const userId = decoded.id;
        req.userId = userId;
    res.setHeader(
      "Access-Control-Allow-Origin",
      "**************************"
    );
    res.setHeader("Access-Control-Allow-Credentials", true);
        next();
      }
    }
  } catch (error) {
    throw new Error("Not authorized!");
  }
};

示例接口代码(/expense/expenses)

router.get("/expenses/", async (req, res) => {
  const userId = req.userId;
  console.log(userId);

  try {
    const expenses = await Expense.find({
      userId: userId,
    })
      .populate("subCategory")
      .populate("category")
      .exec();

    const mappedExpenses = expenses.map((expense) => ({
      id: expense._id,
      date: expense.date,
      price: expense.price,
      userId: expense.userId,
      category: expense.category.name,
      subCategory: expense.subCategory.name,
    }));
    console.log("user expenses: ", mappedExpenses);
    res.status(200).json(mappedExpenses);
  } catch (error) {
    res.status(404).json({ message: error.message });
  }
});

前端RTK Query配置

baseQuery: fetchBaseQuery({baseUrl: import.meta.env.VITE_BASE_URL, credentials: 'include'}),

前端登录设置Cookie代码

loginUser(values)
            .unwrap()
            .then((res) => {
              console.log(res._id);
              setCookie("id", res._id, {
                path: "/",
                sameSite: "none",
                secure: true,
              });

              toast.success("logged in!");

              navigate("/dashboard", { replace: true });
            })
            .catch((e) => {
              console.log(e);
            });

问题根源

  1. CORS头重复设置+错误处理缺失:全局已通过cors中间件配置跨域,但authMiddleware内重复设置响应头;且当无有效id/token时直接抛出错误,未返回合法响应,导致CORS头缺失。
  2. 未捕获异常引发502:authMiddleware抛出的未捕获错误会导致服务器返回500状态码,若有反向代理则会转化为502 Bad Gateway。
  3. 预检请求处理不当:authMiddleware会拦截OPTIONS预检请求,若未通过校验直接抛错,导致预检失败触发CORS错误。

解决方案

1. 重构authMiddleware,移除重复CORS配置

删除中间件内的响应头设置,统一由全局cors中间件处理;同时规范错误返回,避免未捕获异常:

import jwt from "jsonwebtoken";

export const authMiddleware = async (req, res, next) => {
  const { id, token } = req.cookies;
  console.log(id, token);
  try {
    if (id) {
      req.userId = id;
      return next();
    }

    if (token) {
      const decoded = jwt.verify(token, process.env.JWT_SECRET);
      if (decoded) {
        req.userId = decoded.id;
        return next();
      }
    }

    // 无有效凭证时返回401,而非抛错
    return res.status(401).json({ message: "Not authorized!" });
  } catch (error) {
    // 捕获JWT验证错误,返回标准401响应
    return res.status(401).json({ message: "Not authorized! Invalid token" });
  }
};

2. 优化全局CORS配置

用环境变量统一管理前端域名,避免硬编码,确保与请求Origin完全匹配:

app.use(
  cors({
    origin: process.env.FRONTEND_URL, // 例如:https://your-frontend-domain.com
    credentials: true,
  })
);

3. 调整Helmet配置(可选)

若怀疑helmet.crossOriginResourcePolicy与CORS冲突,可暂时注释该行测试,确认无问题后再恢复:

// app.use(helmet.crossOriginResourcePolicy({ policy: "cross-origin" }));

4. 验证前端Cookie配置

确认前端设置Cookie时sameSite: "none"和secure: true已开启,这是跨域携带Cookie的必要条件(当前代码已满足)。

验证步骤

  1. 重启后端服务,确保环境变量FRONTEND_URL与前端域名一致。
  2. 前端发起请求,检查控制台是否仍有CORS错误。
  3. 测试未登录状态下的请求,应返回401 Not authorized而非CORS错误或502。
  4. 确认/expense/expenses请求可正常返回数据或合法错误响应。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 18:59:56