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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 15:57:52