1. Build the project
- Project initialization:
npm i -g @nestjs/cli nest new project-name
- Execute these four commands in the terminal to generate two new modules:
nest g module auth nest g service auth nest g module users nest g service users
- Then deleting these three files is useless:
UserModule
Write UsersService:
import { Injectable } from '@nestjs/common';
interface User {
userId: number;
username: string;
password: string;
}
@Injectable()
export class UsersService {
private readonly users: User[];
constructor() {
// 这里把用户列表写死了. 在真正的应用程序中,用户列表应该从数据库获取
this.users = [
{
userId: 1,
username: 'john',
password: 'riddle',
},
{
userId: 2,
username: 'chris',
password: 'secret',
},
{
userId: 3,
username: 'maria',
password: 'guess',
},
];
}
async findOne(username: string) {
return this.users.find((user) => user.username === username);
}
}
In UsersModule, export UsersService for use by other modules:
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
@Module({
providers: [UsersService],
exports: [UsersService], // 导出 UsersService
})
export class UsersModule {}
AuthModule
Import UserModule in AuthModule:
import { Module } from '@nestjs/common';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
@Module({
imports: [UsersModule], // 导入 UsersModule
providers: [AuthService],
})
export class AuthModule {}
Write AuthService:
import { Injectable } from '@nestjs/common';
import { UsersService } from 'src/users/users.service';
@Injectable()
export class AuthService {
constructor(private readonly usersService: UsersService) {}
/* 检查用户是否已存在 + 校验密码 */
async validateUser(username: string, pwd: string) {
const user = await this.usersService.findOne(username); // 获取用户
if (user && user.password === pwd) {
const { password, ...result } = user; // 剔除 password
return result; // 返回用户信息
}
return null; // 用户不存在 / 密码错误
}
}
2. Local strategy (this step is not jwt yet, this is just login authentication)
在终端上执行这两行代码:
npm i @nestjs/passport passport passport-local
npm i -D @types/passport-local
Add a new configuration file for writing local strategy in the auth directorylocal.strategy.ts< /span>
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
@Injectable() // 通过 PassportStrategy 使用 local 策略
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super();
}
async validate(username: string, password: string) {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException(); // 返回 '未授权' 错误 (401)
}
return user; // 返回用户信息
}
}
Configure the AuthModule to use the Passport attribute just defined:
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport/dist';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';
@Module({
imports: [UsersModule, PassportModule], // 引入 PassportModule
providers: [AuthService, LocalStrategy], // 注册 LocalStrategy
})
export class AuthModule {}
Login test
You can now implement a simple /login route and apply the built-in guards to start the Passport-local flow
import { Controller, Req, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport/dist/auth.guard';
import { Request } from 'express';
@Controller()
export class AppController {
@UseGuards(AuthGuard('local')) // 启用本地策略
@Post('login')
async getHello(@Req() request: Request) {
// Passport 会根据 validate() 方法的返回值创建一个 user 对象
// 并以 req.user 的形式分配给请求对象
return request.user;
}
}
Current project structure:
Start project testing:
Code description:
Add this annotation @UseGuards(AuthGuard('local')) // Enable local strategy, then when requesting '/login', the framework will first execute the class that inherits PassportStrategy The validate method
Note: If it is not written here, it will default to local
PassportStrategy class Local strategy The parameters of the validate method I personally think are variable , but it has not been implemented. After the validate method is completed, the result will be placed in request.user
3.Token strategy
npm i @nestjs/jwt passport-jwt
npm i @types/passport-jwt -D
Introduce JwtModule in AuthModule:
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport/dist';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';
@Module({
imports: [UsersModule, PassportModule, JwtModule], // 引入 JwtModule
providers: [AuthService, LocalStrategy],
})
export class AuthModule {}
Write AuthService and add login()
method:
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt/dist';
import { UsersService } from 'src/users/users.service';
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly jwtService: JwtService,
) {}
async validateUser(username: string, pwd: string) {
const user = await this.usersService.findOne(username);
if (user && user.password === pwd) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(user: any) {
const payload = { username: user.username, sub: user.userId };
return {
// 使用 jwtService.sign() 基于 payload 生成 token 字符串
access_token: this.jwtService.sign(payload),
};
}
}
Update auth.module.ts, configure JwtModule, and export AuthService:
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport/dist';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';
@Module({
imports: [
UsersModule,
PassportModule,
/* 配置 JwtModule */
JwtModule.register({
secret: 'ceshi', // 使用 token 签名密文,我在这里是写固定的,实际开发里面,不能这样
signOptions: { expiresIn: '7d' }, // 设置 token 的有效期,七天
}),
],
providers: [AuthService, LocalStrategy],
exports: [AuthService], // 导出 AuthService
})
export class AuthModule {}
For more information about Nest JwtModule see GitHub - @nestjs/jwt
For more information about available configuration options see GitHub - node-jsonwebtoken
Update the /login route to return the JWT:
import { Controller, Req, Post, UseGuards } from '@nestjs/common';
import { Request } from 'express';
import { AuthGuard } from '@nestjs/passport/dist/auth.guard';
import { AuthService } from './auth/auth.service';
@Controller()
export class AppController {
constructor(private readonly authService: AuthService) {} // 依赖注入
@UseGuards(AuthGuard('local'))
@Post('login')
async getHello(@Req() request: Request) {
return this.authService.login(request.user); // 调用 login 方法
}
}
Check token
Add the configuration file jwt.strategy.ts for writing local strategies in the auth directory.
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromHeader('token'),
ignoreExpiration: false,
secretOrKey: 'ceshi', // 使用 token 签名密文来解密,我在这里是写固定的,实际开发里面,不能这样
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
3.1 Code description
Add JwtStrategy as provider in AuthModule:
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport/dist';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: 'ceshi', // 使用 token 签名密文,我在这里是写固定的,实际开发里面,不能这样
signOptions: { expiresIn: '7d' },
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy], // 注册 JwtStrategy
exports: [AuthService],
})
export class AuthModule {}
Update the app.controller.ts file to use JWT:
import { Controller, Req, Post, UseGuards, Get } from '@nestjs/common';
import { Request } from 'express';
import { AuthGuard } from '@nestjs/passport/dist/auth.guard';
import { AuthService } from './auth/auth.service';
@Controller()
export class AppController {
constructor(private readonly authService: AuthService) {}
@UseGuards(AuthGuard('local'))
@Post('login')
async getHello(@Req() request: Request) {
return this.authService.login(request.user);
}
@UseGuards(AuthGuard('jwt')) // 使用 JWT 鉴权
@Get('profile')
getProfile(@Req() request: Request) {
return request.user; // 返回用户信息
}
}
Default policy
In AppController, using the @UseGuards(AuthGuard(XXX))
decorator requires passing the name of the strategy XXX
. We can declare a default policy so we don't have to pass in a name.
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport/dist';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';
@Module({
imports: [
UsersModule,
PassportModule.register({ defaultStrategy: 'jwt' }), // 配置默认策略
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '60s' },
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
Sample code:
Sample code can be downloaded from here