Passport.js跨站Cookie问题:Chrome更新后认证服务是否失效及解决方案
1. Chrome更新后认证服务会失效吗?
会的。Chrome未来会拦截未分区的第三方Cookie,你的场景里,前端example.com请求后端api.example.com时,后端设置的SameSite=None; Secure会话Cookie会被判定为第三方Cookie(因为跨源),如果没有添加Partitioned属性,更新后Chrome会阻止前端携带这个Cookie到后端,Passport.js无法读取会话ID,自然无法完成用户认证,服务就会失效。
2. 无需变更后端部署位置的解决方案
方案一:给Cookie添加Partitioned属性
给会话Cookie加上Partitioned标记,配合SameSite=None; Secure使用,Chrome会将这个Cookie与前端站点example.com绑定存储,视为“分区Cookie”,不会被第三方拦截规则影响。
如果用express-session(Passport.js依赖的会话中间件),配置示例:
app.use(session({ store: new RedisStore({ client: redisClient }), secret: '你的会话密钥', resave: false, saveUninitialized: false, cookie: { secure: true, sameSite: 'none', partitioned: true, // 关键属性 maxAge: 24 * 60 * 60 * 1000 // 会话过期时间 } }));
这个方案改动最小,只需要添加一个Cookie属性,Passport.js和Redis会话存储不需要额外调整。
方案二:利用同一主域的站点共享Cookie
因为example.com和api.example.com属于同一主域的子域,可以将Cookie的SameSite设为Lax或Strict,同时配置跨域凭证共享,这样Cookie会被视为第一方Cookie,不会触发第三方拦截规则。
后端配置(Express为例):
- 配置CORS允许指定源携带凭证:
const cors = require('cors'); app.use(cors({ origin: 'https://example.com', // 只允许前端域名跨域请求 credentials: true // 允许携带Cookie }));
- 调整会话Cookie设置:
app.use(session({ store: new RedisStore({ client: redisClient }), secret: '你的会话密钥', resave: false, saveUninitialized: false, cookie: { secure: true, sameSite: 'lax', // 或'strict',Lax兼容性更好 domain: '.example.com', // 设置主域,让子域共享Cookie maxAge: 24 * 60 * 60 * 1000 } }));
前端请求配置:
用fetch或axios请求时,必须携带凭证:
// Fetch示例 fetch('https://api.example.com/auth/verify', { credentials: 'include' }); // Axios示例 axios.get('https://api.example.com/auth/verify', { withCredentials: true });
这个方案兼容性最好,是跨子域认证的常规做法,完全避开第三方Cookie限制。
方案三:切换到Token-based认证(JWT)
放弃Cookie会话,改用JWT令牌。前端登录后获取JWT,每次请求后端时在请求头里携带令牌,后端用Passport.js的JWT策略验证身份,完全不依赖Cookie,自然不受第三方Cookie规则影响。
后端配置(Passport JWT):
const passportJWT = require('passport-jwt'); const JwtStrategy = passportJWT.Strategy; const ExtractJwt = passportJWT.ExtractJwt; const opts = { jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // 从请求头提取令牌 secretOrKey: '你的JWT密钥' }; // 定义JWT验证策略 passport.use(new JwtStrategy(opts, (jwtPayload, done) => { // 根据JWT中的用户ID查询数据库 User.findById(jwtPayload.id, (err, user) => { if (err) return done(err, false); if (user) return done(null, user); return done(null, false); }); }));
前端处理:
登录成功后存储Token,后续请求携带:
// 登录请求 fetch('https://api.example.com/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'xxx', password: 'xxx' }) }) .then(res => res.json()) .then(data => { localStorage.setItem('authToken', data.token); // 存储Token }); // 后续请求 const token = localStorage.getItem('authToken'); fetch('https://api.example.com/protected', { headers: { 'Authorization': `Bearer ${token}` } });
注意:JWT要处理过期问题,可搭配Refresh Token使用;存储Token时要注意XSS风险,建议把Refresh Token存在HttpOnly Cookie里,Access Token存在内存中。
内容的提问来源于stack exchange,提问作者Meli Fetaji

