本指南会带你搭建一个使用 brkpt-auth 的最小化 NestJS 应用。
你将从一个新的 NestJS 项目开始,初始化 brkpt-auth,配置 JWT 身份验证,实现所需的适配器,并通过 credentials 功能启用账号密码登录。
完成本指南后,你的应用将支持:
POST /auth/sign-upPOST /auth/sign-inPOST /auth/sign-outPOST /auth/refreshGET /auth/me
- Node.js 20+
创建 NestJS 项目
Section titled “创建 NestJS 项目”安装 NestJS CLI 并创建新项目:
pnpm add -g @nestjs/clinpm install -g @nestjs/cliyarn global add @nestjs/cli创建一个新的 NestJS 项目:
nest new brkpt-auth-get-started --strict然后进入项目目录:
cd brkpt-auth-get-started你可以删除默认的 app.controller.ts、app.controller.spec.ts 和 app.service.ts 文件,本指南不会用到它们。
初始化 brkpt-auth
Section titled “初始化 brkpt-auth”全局安装 brkpt CLI:
pnpm add -g @brkpt/clinpm install -g @brkpt/cliyarn global add @brkpt/cli在项目根目录运行初始化命令:
brkpt auth initCLI 会检测你的项目结构,安装 brkpt-auth 源文件,并列出你需要补充安装的缺失依赖。
core 功能默认会被添加,它提供基础的 JWT 流程,以及 /auth/me、/auth/refresh、/auth/sign-out 等共享端点。
配置 BrkptAuthModule
Section titled “配置 BrkptAuthModule”core 功能需要为 access token 和 refresh token 分别配置。
安装依赖
安装本指南用到的依赖:
pnpm add @nestjs/config @nestjs/event-emitter cookie-parserpnpm add -D @types/cookie-parsernpm install @nestjs/config @nestjs/event-emitter cookie-parsernpm install -D @types/cookie-parseryarn add @nestjs/config @nestjs/event-emitter cookie-parseryarn add -D @types/cookie-parser创建环境变量
在项目根目录创建 .env 文件:
JWT_ACCESS_SECRET="your-access-secret"JWT_REFRESH_SECRET="your-refresh-secret"注册 BrkptAuthModule
在 app.module.ts 中注册 ConfigModule、EventEmitterModule 和 BrkptAuthModule:
import { Module } from '@nestjs/common';import { ConfigModule, ConfigService } from '@nestjs/config';import { EventEmitterModule } from '@nestjs/event-emitter';
import { BrkptAuthModule } from './brkpt-auth/brkpt-auth.module';
@Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), EventEmitterModule.forRoot({ global: true, wildcard: true, }), BrkptAuthModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (config: ConfigService) => ({ jwt: { access: { secret: config.getOrThrow('JWT_ACCESS_SECRET'), expiresIn: '5m', }, refresh: { secret: config.getOrThrow('JWT_REFRESH_SECRET'), expiresIn: '1h', transport: 'cookie', }, }, }), }), ],})export class AppModule {}本指南使用 cookie 方式传输 refresh token。这种模式下,brkpt-auth 会把 refresh token 写入 HttpOnly cookie,客户端不需要手动处理它。
启用 cookie-parser
在 main.ts 中启用 cookie-parser:
import { NestFactory } from '@nestjs/core';import cookieParser from 'cookie-parser';
import { AppModule } from './app.module';
async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(cookieParser()); await app.listen(process.env.PORT ?? 3000);}void bootstrap();准备用户模块
Section titled “准备用户模块”brkpt-auth 不要求特定的数据库 schema、用户模型、ORM 或 repository 结构。
本指南在 UserModule 里用一个小型内存 repository,这样你无需搭建数据库就能跑通身份验证流程。
在实际应用中,你的 UserModule 通常会提供基于 ORM 的服务或 repository,比如 Prisma service、TypeORM repository,或者你自己的用户数据访问 provider。
创建以下文件:
文件夹src/
文件夹user/
文件夹repositories/
- memory-user.repository.ts
- user.entity.ts
- user.module.ts
定义用户类型
创建 user.entity.ts,定义本指南使用的用户结构:
export interface User { id: number; name: string; email: string; password: string;}brkpt-auth 不要求固定的用户结构。本指南的示例应用使用 id、name、email 和 password。你后面编写的适配器会决定哪些字段用于 JWT 载荷、密码校验和安全的用户响应。
创建 MemoryUserRepository
把 memory-user.repository.ts 创建为一个小型内存 repository:
import { Injectable } from '@nestjs/common';
import { User } from '../user.entity';
@Injectable()export class MemoryUserRepository { private users: User[] = []; private currentId = 1;
findOne(predicate: (u: User) => boolean): Promise<User | null> { return Promise.resolve(this.users.find(predicate) || null); }
findAll(): Promise<User[]> { return Promise.resolve([...this.users]); }
create(data: Omit<User, 'id'>): Promise<User> { const newUser: User = { id: this.currentId++, ...data, }; this.users.push(newUser); return Promise.resolve(newUser); }
update(id: number, data: Partial<Omit<User, 'id'>>): Promise<User> { const userIndex = this.users.findIndex((u) => u.id === id); if (userIndex === -1) { throw new Error(`User with id ${id} not found`); } const updated = { ...this.users[userIndex], ...data }; this.users[userIndex] = updated; return Promise.resolve(updated); }
delete(id: number): Promise<User> { const userIndex = this.users.findIndex((u) => u.id === id); if (userIndex === -1) { throw new Error(`User with id ${id} not found`); } const [deleted] = this.users.splice(userIndex, 1); return Promise.resolve(deleted); }}创建 UserModule
创建 user.module.ts,导出 MemoryUserRepository 以便适配器可以注入它:
import { Module } from '@nestjs/common';
import { MemoryUserRepository } from './repositories/memory-user.repository';
@Module({ providers: [MemoryUserRepository], exports: [MemoryUserRepository],})export class UserModule {}接入 core 功能
Section titled “接入 core 功能”core 功能负责基础的 JWT 流程,但它并不知道你的用户模型长什么样。core 适配器告诉 brkpt-auth 如何把用户映射为 JWT 载荷、如何从 JWT 载荷查找用户,以及如何返回安全的用户数据。
创建以下文件:
文件夹src/
文件夹brkpt-auth/
文件夹adapters/
- core.adapter.ts
- types.ts
定义 JWT 载荷
创建 types.ts:
export type AuthJwtPayload = { sub: number; email: string;};实现 CoreAdapter
创建 core.adapter.ts:
import { Injectable } from '@nestjs/common';
import { MemoryUserRepository } from '../../user/repositories/memory-user.repository';import { User } from '../../user/user.entity';import { CorePort } from '../features/core/core.port';import { AuthJwtPayload } from './types';
@Injectable()export class CoreAdapter implements CorePort<User> { constructor(private readonly userRepo: MemoryUserRepository) {}
mapUserToJwtPayload(user: User): AuthJwtPayload { return { sub: user.id, email: user.email, }; }
shrinkJwtPayload(payload: AuthJwtPayload): Record<string, unknown> { return { sub: payload.sub, }; }
findUserByJwtPayload(payload: AuthJwtPayload): Promise<User | null> { return this.userRepo.findOne((u) => u.id === payload.sub); }
toSafeUser(user: User): Record<string, unknown> { const { password: _password, ...safe } = user; return safe; }
extractUserIdFromJwtPayload(payload: AuthJwtPayload): number { return payload.sub; }}每个方法要么是简单的映射,要么是对用户 repository 的直接调用。
注册功能
更新 features.ts,把 CoreAdapter 传给 coreFeature:
import { CoreAdapter } from './adapters/core.adapter';import { FeatureConfig } from './common/interfaces';import { coreFeature } from './features/core/core.feature';
export const features: FeatureConfig[] = [coreFeature(CoreAdapter)];导入 UserModule
CoreAdapter 依赖 MemoryUserRepository,而它是由 UserModule 导出的。
把 UserModule 添加到 brkpt-auth.module.ts:
import { UserModule } from '../user/user.module';
@Module({ imports: [], imports: [UserModule], controllers: [...features.flatMap((f) => f.controllers)], providers: [...features.flatMap((f) => f.providers)],})export class BrkptAuthModule { // ...}到这一步,core 功能已经能从 JWT 载荷解析用户并返回安全的用户数据。
如果此时启动应用并调用 GET /auth/me,会返回 401 Unauthorized。这是预期行为,因为应用还没有注册或登录端点。
添加 credentials 功能
Section titled “添加 credentials 功能”core 功能不包含账号注册或登录,这是有意为之:你可以自行选择应用需要的登录方式。
本指南使用 credentials 功能实现账号密码登录。
添加功能
运行 CLI 命令:
brkpt auth add credentialsCLI 会添加一个新的 features/credentials/ 文件夹。
定义 DTO
credentials 功能默认生成空的 DTO 类。brkpt-auth 不会假设你的注册和登录请求使用哪些字段。
更新 sign-up.dto.ts:
export class SignUpDto { name!: string; email!: string; password!: string;}更新 sign-in.dto.ts:
export class SignInDto { email!: string; password!: string;}安装 bcrypt
本指南用 bcrypt 来哈希和校验密码:
pnpm add bcryptpnpm add -D @types/bcryptnpm install bcryptnpm install -D @types/bcryptyarn add bcryptyarn add -D @types/bcrypt实现 CredentialsAdapter
创建 credentials.adapter.ts:
import { Injectable } from '@nestjs/common';import * as bcrypt from 'bcrypt';
import { MemoryUserRepository } from '../../user/repositories/memory-user.repository';import { User } from '../../user/user.entity';import { CredentialsPort } from '../features/credentials/credentials.port';import { SignInDto } from '../features/credentials/dto/sign-in.dto';import { SignUpDto } from '../features/credentials/dto/sign-up.dto';
@Injectable()export class CredentialsAdapter implements CredentialsPort<User> { constructor(private readonly userRepo: MemoryUserRepository) {}
findUserByDto(dto: SignInDto | SignUpDto): Promise<User | null> { return this.userRepo.findOne((u) => u.email === dto.email); }
validatePassword(user: User, dto: SignInDto): Promise<boolean> { return bcrypt.compare(dto.password, user.password); }
async createUser(dto: SignUpDto): Promise<User> { const password = await bcrypt.hash(dto.password, 10); return this.userRepo.create({ name: dto.name, email: dto.email, password: password, }); }
extractUserIdFromUser(user: User): number { return user.id; }}密码策略完全由适配器控制。只需修改 validatePassword 和 createUser,就能把 bcrypt 换成 argon2 或其他任意哈希库。
注册功能
更新 features.ts,把 CredentialsAdapter 传给 credentialsFeature:
import { CoreAdapter } from './adapters/core.adapter';import { CredentialsAdapter } from './adapters/credentials.adapter';import { FeatureConfig } from './common/interfaces';import { coreFeature } from './features/core/core.feature';import { credentialsFeature } from './features/credentials/credentials.feature';
export const features: FeatureConfig[] = [ coreFeature(CoreAdapter), credentialsFeature(CredentialsAdapter),];CredentialsAdapter 用的是同一个 MemoryUserRepository,不需要额外导入其他模块。
校验请求数据
Section titled “校验请求数据”brkpt-auth 不强制要求使用某个校验库。本指南使用标准的 NestJS ValidationPipe 配合 class-validator。
安装校验依赖
安装依赖:
pnpm add class-validator class-transformernpm install class-validator class-transformeryarn add class-validator class-transformer启用 ValidationPipe
在 main.ts 中启用全局校验管道:
import { ValidationPipe } from '@nestjs/common';import { NestFactory } from '@nestjs/core';import cookieParser from 'cookie-parser';
import { AppModule } from './app.module';
async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(cookieParser()); app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, }), ); await app.listen(process.env.PORT ?? 3000);}void bootstrap();为 DTO 添加校验
为 sign-up.dto.ts 添加校验装饰器:
import { IsEmail, IsString, MinLength } from 'class-validator';
export class SignUpDto { @IsString() name!: string;
@IsEmail() email!: string;
@IsString() @MinLength(6) password!: string;}为 sign-in.dto.ts 添加校验装饰器:
import { IsEmail, IsString } from 'class-validator';
export class SignInDto { @IsEmail() email!: string;
@IsString() password!: string;}启动应用:
pnpm start:devnpm run start:devyarn start:dev现在可以使用以下端点:
| Method | Path | Description |
|---|---|---|
POST |
/auth/sign-up |
创建用户 |
POST |
/auth/sign-in |
登录 |
POST |
/auth/sign-out |
登出 |
POST |
/auth/refresh |
签发新的 access token |
GET |
/auth/me |
返回当前用户 |
用任意 HTTP 客户端测试这些端点,比如 Postman、Bruno 或 Insomnia。
注册
发送注册请求:
POST /auth/sign-upContent-Type: application/json
{ "name": "Kevin", "password": "password123"}响应中包含 accessToken,并把 refresh token 设置为 HttpOnly cookie。
登录
用同一个账号发送登录请求:
POST /auth/sign-inContent-Type: application/json
{ "password": "password123"}和注册一样,响应中包含 accessToken,并设置 refresh token cookie。
获取当前用户
带上 access token 调用 /auth/me:
GET /auth/meAuthorization: Bearer <access-token>响应返回当前用户,不含密码字段。
刷新 access token
带上注册或登录时设置的 refresh token cookie,调用 /auth/refresh:
POST /auth/refreshCookie: refreshToken=<refresh-token>响应中包含新的 accessToken。
登出
带上 access token 调用 /auth/sign-out:
POST /auth/sign-outAuthorization: Bearer <access-token>在 cookie 传输模式下,登出会清除 refresh token cookie(如果存在的话)。
按顺序继续阅读后续指南,替换内存 repository、添加会话管理,并逐个功能地扩展你的身份验证流程。