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

生产环境PassportJS+Express+React授权Cookie异常求助

问题描述

后端基于PassportJS+Express,前端React+Axios搭建的应用,本地运行时授权功能完全正常:客户端发送携带邮箱密码的POST请求,服务器返回Set-Cookie响应头,浏览器可正常存储Cookie。但部署到Vercel后出现以下问题:

  1. 客户端请求后,响应头存在Set-Cookie,但浏览器未存储该Cookie
  2. 直接通过服务器URL访问安全接口可正常返回数据,用Axios发起相同请求时,请求头携带的Cookie与直接访问时一致,却返回401未通过权限校验,偶尔又能正常通过校验获取数据

已配置的生产环境代码

CORS与Session配置

app.use(
  cors({
    origin: process.env.ADMIN_PANEL_URL,
    methods: 'GET,POST,PUT,DELETE',
    credentials: true,
    exposedHeaders: ['set-cookie'],
  })
);

app.set('trust proxy', 1);

app.use(
  session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: true,
    cookie: {
      sameSite: 'none',
      secure: true,
      maxAge: 1000 * 60 * 60 * 24,
    },
  })
);

客户端Axios配置与请求代码

const api = axios.create({
  baseURL: `${process.env.REACT_APP_SERVER_URL}`,
  withCredentials: true,
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json",
    "Access-Control-Expose-Headers": "Set-Cookie",
  },
});

static async getStatistics() {
    return (
      await api.get<IStatistic>(`/statistics`, {
        withCredentials: true,
      })
    ).data;
  }

static localLogin(
    email: string,
    password: string,
    setState: Dispatch<SetStateAction<boolean>>,
    setAlert: Dispatch<
      SetStateAction<{
        isOpen: boolean;
        message: string;
        severity: AlertColor;
      }>
    >
  ) {
    axios({
      method: "POST",
      data: {
        email: email,
        password: password,
      },
      withCredentials: true,
      url: `${process.env.REACT_APP_SERVER_URL}/local`,
    })
      .then((res) => {
        if (res.status === 200) {
          if (res.data.isAdmin) {
            localStorage.setItem(`isAuth`, JSON.stringify(true));
            window.location.href = `${process.env.REACT_APP_CLIENT_URL}`;
          } else {
            setState(true);
          }
        }
      })
      .catch((e) => {
        console.log(e);
        if (e.code !== "ERR_NETWORK" && !e.response.data.success) {
          setState(true);
        } else {
          setAlert({
            isOpen: true,
            message: "Server error, try again later",
            severity: "error",
          });
        }
        console.log(e);
      });
  }

Passport LocalStrategy配置

router.post(
  '/local',
  passport.authenticate('local', {
    failureRedirect: '/login/fail',
  }),
  (req, res) => {
    res.json(req.user);
  }
);

passport.use(
  'local',
  new PassportLocal.Strategy(
    { usernameField: 'email' },
    async (email, password, done) => {
      try {
        const user = await User.findOne({ email: email.toLowerCase() });
        if (!user || !(await bcrypt.compare(password, user.password))) {
          done(null, false);
        }
        done(null, user);
      } catch (e) {
        done(e);
      }
    }
  )
);

passport.serializeUser((user, done) => {
  return done(null, user._id.toString());
});

passport.deserializeUser((id, done) => {
  User.findById(id, (err, user) => {
    return done(err, user);
  });
});

权限校验中间件

export const isAdminLoggedIn = (req, res, next) => {
  req.isAuthenticated() && req.user.isAdmin ? next() : res.sendStatus(401);
};
解决方案

针对Vercel部署后的Cookie异常问题,按以下步骤排查修复:

1. 给Session Cookie添加domain配置

Vercel部署的前后端通常属于不同子域名,浏览器默认不会跨域存储Cookie,必须显式指定主域名:
修改Session配置中的cookie字段:

cookie: {
  sameSite: 'none',
  secure: true,
  maxAge: 1000 * 60 * 60 * 24,
  domain: '.your-domain.com', // 替换为你的主域名,如前端是admin.your-domain.com、后端是api.your-domain.com时,设为该值
  httpOnly: true // 建议添加,提升Cookie安全性,防止XSS攻击
}

注意:domain必须以.开头,确保所有子域名都能共享该Cookie;若前后端为同域名不同路径可省略,但Vercel部署场景下一般需要配置。

2. 确保CORS的origin精确匹配

Vercel部署后,前端实际URL可能与配置的ADMIN_PANEL_URL不一致(如带/不带www、使用Vercel临时域名),浏览器会严格校验origin,不匹配则拒绝存储Cookie:

  • 检查环境变量ADMIN_PANEL_URL的值,必须与前端部署后的实际URL完全一致(包含https://,结尾不要加/)
  • 测试阶段可临时将origin设为true(生产环境禁止使用),验证是否为origin匹配问题:
cors({
  origin: true, // 临时测试用,生产必须设为精确域名
  credentials: true,
  // 其他配置保持不变
})

3. 统一使用Axios实例,避免配置不一致

你的localLogin方法使用了全局axios,而非预先创建的api实例,虽然都配置了withCredentials: true,但统一实例可避免潜在的配置差异:
修改localLogin方法:

static localLogin(
    email: string,
    password: string,
    setState: Dispatch<SetStateAction<boolean>>,
    setAlert: Dispatch<
      SetStateAction<{
        isOpen: boolean;
        message: string;
        severity: AlertColor;
      }>
    >
  ) {
    api.post('/local', { email, password })
      .then((res) => {
        // 原有逻辑保持不变
      })
      .catch((e) => {
        // 原有逻辑保持不变
      });
  }

另外,api实例已配置withCredentials: true,请求时无需重复添加,避免冗余。

4. 将Session从内存改为持久化存储(关键修复)

Vercel的Serverless函数是无状态的,每次请求可能分配到不同的函数实例,内存Session无法跨实例共享,这是偶尔出现401的核心原因。需改用Redis等持久化存储,推荐使用Upstash(Vercel官方推荐的Redis服务):

  1. 安装依赖:npm install connect-redis redis
  2. 修改Session配置:
const RedisStore = require('connect-redis').default;
const redis = require('redis');

// 从Upstash获取Redis连接URL,存入环境变量
const client = redis.createClient({
  url: process.env.REDIS_URL
});
client.connect().catch(console.error);

app.use(
  session({
    store: new RedisStore({ client }), // 使用Redis存储Session
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false, // 改为false,避免创建空Session
    cookie: {
      sameSite: 'none',
      secure: true,
      maxAge: 1000 * 60 * 60 * 24,
      domain: '.your-domain.com',
      httpOnly: true
    },
  })
);

5. 移除Passport的重定向逻辑,直接返回JSON响应

passport.authenticate中的failureRedirect会在认证失败时触发重定向,可能导致Set-Cookie头被浏览器忽略,改为直接返回JSON响应:

router.post(
  '/local',
  (req, res, next) => {
    passport.authenticate('local', (err, user, info) => {
      if (err) return next(err);
      if (!user) return res.status(401).json({ success: false, message: '账号或密码错误' });
      req.logIn(user, (err) => {
        if (err) return next(err);
        return res.json(user);
      });
    })(req, res, next);
  }
);

这样认证失败时不会触发跳转,浏览器可正确处理响应头中的Set-Cookie。

6. 检查浏览器第三方Cookie设置

Chrome等现代浏览器默认阻止第三方Cookie,若前后端为不同域名,需确保浏览器允许你的域名的第三方Cookie,或确认SameSite=None与secure: true已正确配置(你已配置,可再次核对)。
Chrome中可通过chrome://settings/cookies查看是否阻止了目标域名的Cookie。

总结

最核心的两个问题:

  • Vercel Serverless环境下内存Session无法跨实例共享,必须改用Redis持久化存储
  • Cookie未配置domain,导致跨子域名无法存储

按上述步骤逐一排查修复,即可解决部署后的Cookie授权问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 22:34:56