Node.js部署Vercel后Google认证无Session ID问题求助
我通过Node.js、Express.js搭配passport-google-oauth20与express-session实现谷歌账号登录功能,本地环境运行正常,但将Vite全栈应用部署到Vercel后出现异常——无法创建Session ID,且deserializeUser函数未执行。以下是后端Index.js代码,请求排查谷歌认证成功后无Session ID返回的原因:
//SET UP CORS MIDDLEWARE app.use( cors({ origin: true, credentials: true, }) ); //MIDDLEWARE TO PARSE DATA IN REQUEST BODY AND MAKE IT AVAILABLE IN REQ.BODY app.use(express.json()); //APPLY HELMET TO ADD SOME HTTP HEADERS FOR SECURITY ENHANCEMENT app.use(helmet()); //APPLY COOKIE PARSER TO PARSE DATA STORED IN COOKIE AND MAKE IT AVAILABLE IN REQ.COOKIES app.use(cookieparser()); //SET UP THE EXPRESS SESSION MIDDLEWARE app.use( expressSession({ secret: SESSION_SECRET, resave: false, saveUninitialized: true, cookie: { secure: true, maxAge: 60 * 60 * 1000, httpOnly: true, sameSite: "none", }, }) ); //INITIALIZE PASSPORT app.use(passport.initialize()); // PASSPORT SESSION CALL THE DESERIALIZEUSER FUNCTION AND PASS THE USER ID TO THE PARAMETER OF THIS FUNCTION app.use(passport.session()); //IMPLEMENT GOOGLE STRATEGY passport.use( new GoogleStrategy( { clientID: GOOGLE_CLIENT_ID, clientSecret: GOOGLE_CLIENT_SECRET, callbackURL: `https://nasa-app-backend.vercel.app/auth/google/callback`, }, function (_accessToken, _refreshToken, profile, done) { const { id: google_id, name: nameObj } = profile; //CHECK TO SEE IF THIS USER IS IN DATABASE User.findOne({ google_id: google_id, username: nameObj.givenName }) .then((user) => { //IF THIS USER EXISTS IN DATABASE if (user) { done(null, { _id: user._id }); } //IF THIS IS A NEW USER else { User.create({ google_id: google_id, username: nameObj.givenName }) .then((res) => { done(null, { _id: res._id }); }) .catch((e) => console.log(`Error creating a user ${e}`)); } }) .catch((e) => console.log(`Error fetching a user ${e}`)); } ) ); //SET UP SERIALIZEUSER passport.serializeUser((userObj, done) => { console.log(userObj); done(null, userObj._id); }); //SSET UP DESERIALIZEUSER passport.deserializeUser((userId, done) => { console.log(userId); User.findOne({ _id: userId }) .then((user) => done(null, { _id: user._id, username: user.username })) .catch((e) => { console.log("Error finding user"); }); }); //CONNECT TO THE MONGODB DATABASE handleConnectMongoDBServer(); //APPLY ROUTES app.use("/auth", googleAuthRoute); //Put the /auth first to ensure that req.user is set before the JWT check token middlewares are invoked app.use("/user-profile", userProfileRoute); app.use("/login", loginRoute); app.use("/signup", signupRoute); app.use("/logout", logoutRoute); //START THE SERVER app.listen(PORT, (req, res) => { console.log(`Website is listening to the port http://localhost:${PORT}`); });
核心排查点及解决方法
1. express-session默认内存存储不兼容Vercel无服务器环境
express-session默认用内存存储Session,但Vercel的API路由是无状态的,每次请求可能分配到不同的实例,内存中的Session无法跨实例共享,导致Session ID无法持久化。
解决:改用持久化会话存储,比如connect-mongo(基于MongoDB的会话存储):
- 安装依赖:
npm install connect-mongo - 修改session配置:
const MongoStore = require('connect-mongo'); // 信任Vercel反向代理,确保HTTPS识别正确 app.set('trust proxy', 1); app.use( expressSession({ secret: SESSION_SECRET, resave: false, saveUninitialized: false, // 避免创建空会话,更安全 store: MongoStore.create({ mongoUrl: process.env.MONGODB_URI, // 你的MongoDB连接字符串 ttl: 60 * 60 // 会话过期时间,和cookie maxAge保持一致 }), cookie: { secure: process.env.NODE_ENV === 'production', // 生产环境启用HTTPS cookie,本地开发设为false maxAge: 60 * 60 * 1000, httpOnly: true, sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax', // 本地用lax,生产用none配合secure }, }) );
2. 未信任Vercel反向代理导致Cookie设置失败
Vercel通过反向代理转发请求,express默认不会信任代理的HTTPS标识,导致secure: true的Cookie无法正确设置。添加app.set('trust proxy', 1)让express信任代理,才能正确识别请求的协议(HTTPS)。
3. deserializeUser未执行的连锁问题
deserializeUser不执行的根本原因是Session未被持久化,passport.session()无法从Session中读取用户ID,自然不会触发该函数。解决Session存储问题后,该函数会自动正常执行。
另外注意:当前deserializeUser的catch块只打印日志未调用done(e),会导致请求挂起,建议修改为:
passport.deserializeUser((userId, done) => { console.log(userId); User.findOne({ _id: userId }) .then((user) => done(null, { _id: user._id, username: user.username })) .catch((e) => { console.log("Error finding user", e); done(e); // 必须调用done传递错误 }); });
4. CORS配置优化
当前CORS的origin: true会允许所有来源,生产环境建议指定具体的前端域名,避免安全风险:
app.use( cors({ origin: process.env.FRONTEND_URL, // 比如https://your-vite-app.vercel.app credentials: true, }) );
同时确保前端请求时携带credentials: 'include',比如:
fetch('/auth/google', { credentials: 'include' });
内容的提问来源于stack exchange,提问作者Thành Nhân

