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

跨域应用中Express Session模块无法保存会话Cookie问题排查

问题描述

本地开发环境下,Node.js服务运行在http://localhost:3002,Vite托管的客户端运行在http://localhost:5173。尝试使用express-session实现用户认证,服务器向客户端发送会话Cookie,但每次请求都会生成新会话,Cookie未正常传递。

服务器代码

this.#app.use(cookieParser());

this.#app.use(cors({
    origin: WEB_CLIENT_URL,
    credentials: true,
    methods:'GET, POST',
    allowedHeaders:'Origin, X-Requested-With, Content-Type, Accept, authorization'
}));

var sess = {
    secret: 'keyboard cat',
    saveUninitialized: false,
    resave: false,
    cookie: {
        secret: 'yourSecret',
        secure: process.env.NODE_ENV === 'production',
        httpOnly: process.env.NODE_ENV === 'production',
        sameSite: "none" as "none",
        maxAge: 24 * 60 * 60 * 1000,  // 24 hours
        domain: undefined//'localhost:3002'
    },
}
if (process.env.NODE_ENV === 'production') {
    this.#app.set('trust proxy', 1) // trust first proxy
}
this.#app.use(session(sess));

this.#app.use(async (req, res, next)=> {

    // Website you wish to allow to connect
    res.setHeader('Access-Control-Allow-Origin', req.headers.origin ?? "");

    // Request methods you wish to allow
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE');
    // Request headers you wish to allow
    res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type, Authorization');
    // Set to true if you need the website to include cookies in the requests sent
    // to the API (e.g. in case you use sessions)
    res.setHeader('Access-Control-Allow-Credentials', 'true');

    const session = req.session as Session;
    console.log(req.session.id, req.method, req.originalUrl);
    if (!session.user) {
        const access = await TokenCollection.CreateAccessToken();
        session.user = access.token;
        req.session.save((err) => {
            if (err) {
                next(Utils.GetError('Error creating session', 500));
            } else {
                next();
            }
        }); 
    } else {
        console.log("FOUND USER");
        next();
    } 
});

客户端代码

response = await fetch("http://localhost:3002/api/user", {
    method: "POST",
    body: JSON.stringify(profile_form_data),
    headers: {
        'authorization': `Bearer ${access.token}`,
        'content-type': 'application/json'
    },
    credentials: 'include'
});

注:当前处于开发模式,process.env.NODE_ENV === 'production'为false


问题分析与修复方案

1. CORS配置重复冲突

已经通过cors中间件配置了跨域规则,后续又手动设置Access-Control-*响应头,导致规则不一致——尤其是Access-Control-Allow-Origin被动态设置为请求来源,和cors中间件中指定的WEB_CLIENT_URL可能冲突,浏览器会因规则不匹配拒绝携带Cookie。

修复:移除手动设置的所有Access-Control-*头,完全依赖cors中间件处理跨域,仅保留会话逻辑:

// 删除手动设置跨域头的代码块,保留会话处理逻辑
this.#app.use(async (req, res, next)=> {
    const session = req.session as Session;
    console.log(req.session.id, req.method, req.originalUrl);
    if (!session.user) {
        const access = await TokenCollection.CreateAccessToken();
        session.user = access.token;
        req.session.save((err) => {
            if (err) {
                next(Utils.GetError('Error creating session', 500));
            } else {
                next();
            }
        }); 
    } else {
        console.log("FOUND USER");
        next();
    } 
});

2. Cookie配置错误

开发模式下使用http协议,当前Cookie配置存在两个关键问题:

  • sameSite: "none":浏览器要求sameSite=none必须配合secure: true(仅HTTPS环境有效),开发模式下secure为false,浏览器会直接拒绝存储该Cookie。
  • httpOnly: false:开发模式下也应该开启httpOnly,这是安全最佳实践,且不影响浏览器自动携带Cookie(前端本就不应读取会话Cookie)。

修复:调整Cookie配置,适配开发环境:

var sess = {
    secret: 'keyboard cat',
    saveUninitialized: true, // 修改为true,确保会话能被初始保存
    resave: false,
    cookie: {
        secure: process.env.NODE_ENV === 'production',
        httpOnly: true, // 始终开启HttpOnly
        sameSite: process.env.NODE_ENV === 'production' ? "none" : "lax", // 开发用lax,生产用none(配合secure)
        maxAge: 24 * 60 * 60 * 1000,
        domain: process.env.NODE_ENV === 'production' ? '.yourdomain.com' : undefined // 开发环境无需设置domain
    },
}

3. saveUninitialized配置问题

原配置saveUninitialized: false意味着只有当会话被修改后才会保存,但第一次创建会话时,即使调用了req.session.save(),如果Cookie未被客户端存储,下次请求仍会生成新会话。设置为true可以确保初始会话被创建并发送Cookie。

4. 验证WEB_CLIENT_URL值

确保WEB_CLIENT_URL被正确设置为'http://localhost:5173',必须和客户端运行的地址完全一致,包括协议(http)和端口。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 15:45:41