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

如何在TRPC独立服务器中为注册用户存储会话Cookie?

问题

我是一名前端开发者,正在学习后端与TRPC,已编写如下用户注册流程代码:

注册流程代码

export const registration = publicProcedure
  .input(RegistrationFormSchema)
  .mutation(async ({ input: { email, password }, ctx }) => {
    const existingUser = await prisma.user.findUnique({ where: { email } });

    if (existingUser) {
      Failures.throwCustomError(
        { code: 'CONFLICT', message: `Email ${email} already exists` },
        { procedure: PROCEDURE_NAME },
      );
    }

    try {
      const hashedPassword = await hash(password, SALT_ROUNDS);
      const user = await prisma.user.create({ data: { email, password: hashedPassword } });

      console.log({ ctx });
      return { success: true, data: user };
    } catch (error) {
      if (error instanceof Error) {
        Failures.handleServerError(error, {
          procedure: PROCEDURE_NAME,
          metadata: { problem: 'Error while registering user.' },
        });
      }
    }
  });

疑问

查阅TRPC授权章节后,发现文档仅介绍了如何传递授权用户信息,但Context对象并未暴露setHeader之类的方法。请问如何在TRPC独立适配器中将会话存储到Cookie中?或者我的思路存在问题?


解决方案

你的思路没问题,TRPC本身不直接提供设置Cookie的方法,但可以通过在Context中注入响应对象来实现操作Cookie的能力,具体步骤如下:

1. 定义包含响应对象的Context

根据你使用的独立适配器(如express、node-http、fastify),在创建Context时把请求/响应对象注入进去,以express适配器为例:

import type { inferAsyncReturnType } from '@trpc/server';
import type { CreateExpressContextOptions } from '@trpc/server/adapters/express';

// 假设你已经初始化了Prisma实例
import { prisma } from './prisma';

export const createContext = ({ req, res }: CreateExpressContextOptions) => {
  return {
    req,
    res, // 把响应对象注入Context
    prisma,
  };
};

export type Context = inferAsyncReturnType<typeof createContext>;

2. 在注册Mutation中设置Cookie

生成会话凭证(比如JWT)后,通过Context中的响应对象设置Cookie:

import jwt from 'jsonwebtoken'; // 用JWT生成会话token,也可以用其他会话方案

export const registration = publicProcedure
  .input(RegistrationFormSchema)
  .mutation(async ({ input: { email, password }, ctx }) => {
    const existingUser = await ctx.prisma.user.findUnique({ where: { email } });

    if (existingUser) {
      Failures.throwCustomError(
        { code: 'CONFLICT', message: `Email ${email} already exists` },
        { procedure: PROCEDURE_NAME },
      );
    }

    try {
      const hashedPassword = await hash(password, SALT_ROUNDS);
      const user = await ctx.prisma.user.create({ data: { email, password: hashedPassword } });

      // 生成会话token
      const sessionToken = jwt.sign({ userId: user.id }, 'your-secure-secret-key', { expiresIn: '7d' });
      
      // 通过ctx.res设置Cookie(express适配器的res.cookie方法)
      ctx.res.cookie('session', sessionToken, {
        httpOnly: true, // 禁止前端JS访问,防范XSS
        secure: process.env.NODE_ENV === 'production', // 生产环境强制HTTPS
        maxAge: 7 * 24 * 60 * 60 * 1000, // 7天有效期
        sameSite: 'lax', // 防范CSRF
      });

      return { success: true, data: user };
    } catch (error) {
      if (error instanceof Error) {
        Failures.handleServerError(error, {
          procedure: PROCEDURE_NAME,
          metadata: { problem: 'Error while registering user.' },
        });
      }
    }
  });

3. 不同适配器的Cookie设置差异

  • node-http原生适配器:需要手动拼接Set-Cookie响应头:
ctx.res.setHeader('Set-Cookie', `session=${sessionToken}; HttpOnly; Secure=${process.env.NODE_ENV === 'production'}; Max-Age=${7*24*60*60}; SameSite=Lax`);
  • Fastify适配器:使用ctx.res.setCookie方法(Fastify内置的Cookie工具):
ctx.res.setCookie('session', sessionToken, {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  maxAge: 7 * 24 * 60 * 60,
  sameSite: 'lax',
});

内容的提问来源于stack exchange,提问作者seven

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 10:20:37