Nest.js与Angular Universal中Session Cookies失效问题排查
问题:Angular Universal SSR中ngOnInit触发的POST请求无法持久化express-session自定义属性
我使用express-session结合Nest.js和Angular Universal开发服务端渲染(SSR)应用,采用Session Cookies做身份验证。我在Session Cookie中设置了自定义属性tokens并发送至浏览器,但后续请求服务器时,req.session.tokens始终为undefined,不过req.session本身是已定义的。
相关代码
Angular组件ngOnInit代码
export class LoginRequestAccessComponent implements OnInit { constructor(private httpClient: HttpClient) {} ngOnInit() { const options = {withCredentials: true} // 向服务器发送POST请求,在session cookie中设置属性 this.httpClient.post("http://localhost:4200/api/auth", null, options) .subscribe() } }
服务器端POST接口
@Post('auth') async tokens(@Req() req: RequestModel) { // 简化版示例代码 req.session.tokens = { access_token: 'some-generated-token', token_type: 'Bearer', expires_in: 7200, refresh_token: 'some-generated-refresh-token', scope: 'custom_scopes', created_at: 1677607669 } return }
按钮onClick方法
onClick() { const options = {withCredentials: true} this.httpClient.get("http://localhost:4200/api/auth", options) .subscribe() }
服务器端GET接口
@Get('auth') async checkSessionTokens(@Req() req: RequestModel) { console.log(req.session.tokens) // 此处输出undefined }
RequestModel定义
import { Request } from 'express'; export interface RequestModel extends Request { session: SessionModel; }
SessionModel定义
import { Session } from "express-session"; export interface SessionModel extends Session { tokens: Object }
express-session配置
app.use( session({ name: 'My-Custom-Cookie-Name', secret: 'My-Super-Secret', resave: false, saveUninitialized: true, cookie: { secure: false, httpOnly: false }, })
异常现象
浏览器中可以看到Cookie,且请求已携带该Cookie,但:
- 只有
ngOnInit中调用POST接口时会出现问题,后续GET请求拿不到tokens - 若通过按钮触发POST请求,后续所有请求都能正常获取
req.session.tokens - 使用Postman测试完全正常
解决方案
1. 区分SSR与浏览器环境执行请求
Angular Universal的ngOnInit会在服务器端预渲染阶段执行一次,此时发送的POST请求是从SSR服务发向后端,对应的Session存储在服务器内存中,和浏览器端的Session无关。需要只在浏览器环境执行该请求:
import { Component, OnInit, Inject, PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; export class LoginRequestAccessComponent implements OnInit { constructor( private httpClient: HttpClient, @Inject(PLATFORM_ID) private platformId: Object ) {} ngOnInit() { // 仅在浏览器环境发送请求 if (isPlatformBrowser(this.platformId)) { const options = {withCredentials: true} this.httpClient.post("http://localhost:4200/api/auth", null, options) .subscribe() } } }
2. 显式保存Session
在后端POST接口中显式调用req.session.save(),确保Session属性被持久化:
@Post('auth') async tokens(@Req() req: RequestModel) { req.session.tokens = { access_token: 'some-generated-token', token_type: 'Bearer', expires_in: 7200, refresh_token: 'some-generated-refresh-token', scope: 'custom_scopes', created_at: 1677607669 } // 显式保存Session,避免异步导致的丢失 await new Promise((resolve, reject) => { req.session.save((err) => { if (err) reject(err); else resolve(true); }); }); return }
3. 调整Cookie的SameSite属性
未设置SameSite时默认值为Lax,可能在SSR场景下导致Cookie无法正确关联Session。可尝试调整配置:
app.use( session({ name: 'My-Custom-Cookie-Name', secret: 'My-Super-Secret', resave: false, saveUninitialized: true, cookie: { secure: false, httpOnly: false, sameSite: 'lax' // 或尝试 'strict' }, })
内容的提问来源于stack exchange,提问作者Mattias Törnqvist
相关产品推荐
相关产品推荐

