Firebase Functions+Express响应无法设置Cookie问题排查求助
问题背景
在Firebase Hosting部署的网站中,通过Firebase Cloud Functions实现用户登录功能,前端调用/login接口时已携带credentials: "include",但后端返回的__session Cookie始终无法在浏览器中保存。尝试过cloudfunctions.net域名和自定义域名,调整过Cookie的domain、sameSite等参数组合均无效,且未开启第三方Cookie拦截器,相同逻辑在其他项目可正常运行。
前端调用代码
await fetch("https://us-central1-xxxx-website.cloudfunctions.net/api/login", { method: "POST", credentials: "include", headers: { "Accept": "application/json", "Content-Type": "application/json" }, body: JSON.stringify({ token }) })
后端设置Cookie代码
app.post("/login", async (req, res) => { ... res.cookie("__session", sessionCookie, { expires: new Date(new Date().getTime() + expiresIn), // 有效期2周 httpOnly: false, secure: true, sameSite: 'lax', domain: req.get('host') }); return res.sendStatus(200); })
排查与解决步骤
1. 修复CORS配置
Firebase Cloud Functions的CORS必须明确允许凭据传递,且origin不能使用通配符*,需指定你的Firebase Hosting域名:
const cors = require('cors'); app.use(cors({ origin: 'https://your-custom-domain.com', // 替换为实际Hosting域名 credentials: true }));
如果使用自定义域名,确保Functions和Hosting属于同一主域(如app.yourdomain.com和functions.yourdomain.com),否则会被浏览器判定为第三方Cookie拦截。
2. 调整Cookie参数
- domain设置:避免使用
req.get('host'),跨域场景下应设置为你的主域(如.yourdomain.com),或省略domain让浏览器自动匹配当前请求域名;同域场景下可直接省略domain。 - httpOnly:建议设为
true,无需前端读取Cookie,浏览器会自动在请求时携带,更安全。 - sameSite与secure组合:跨域场景下必须设
sameSite: 'none'且secure: true;同域场景可用lax或strict。
调整后的示例:
res.cookie("__session", sessionCookie, { expires: new Date(Date.now() + expiresIn), httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax', domain: '.yourdomain.com' // 自定义主域,适配子域共享 });
3. 配置Hosting重写规则(推荐)
在firebase.json中添加重写规则,让前端通过同一域名调用Functions接口,彻底避免跨域问题:
{ "hosting": { "rewrites": [ { "source": "/api/**", "function": "api" } ] } }
此时前端请求地址改为/api/login,无需跨域,Cookie保存逻辑更稳定。
4. 验证会话令牌有效性
确保sessionCookie是通过admin.auth().createSessionCookie(idToken, { expiresIn })正确生成的,可通过admin.auth().verifySessionCookie(sessionCookie)验证令牌有效性,排除令牌生成错误导致的Cookie不生效。
内容的提问来源于stack exchange,提问作者Vincent Teyssier

