NestJS+Fastify+Passport全局Guard如何按装饰器切换认证策略
To dynamically switch Passport strategies based on your @SetAccessRight decorator, follow these steps:
1. Ensure Strategies Have Unique Names
First, register each JWT strategy with a unique name using the second argument of PassportStrategy:
// src/auth/strategies/jwt-access.strategy.ts import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy, ExtractJwt } from 'passport-jwt'; @Injectable() export class JwtAccessStrategy extends PassportStrategy(Strategy, 'jwt-access') { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_ACCESS_SECRET, }); } async validate(payload: any) { return { userId: payload.sub, username: payload.username }; } } // src/auth/strategies/jwt-refresh.strategy.ts @Injectable() export class JwtRefreshStrategy extends PassportStrategy(Strategy, 'jwt-refresh') { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_REFRESH_SECRET, }); } async validate(payload: any) { return { userId: payload.sub }; } } // src/auth/strategies/jwt-confirm.strategy.ts @Injectable() export class JwtConfirmStrategy extends PassportStrategy(Strategy, 'jwt-confirm') { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_CONFIRM_SECRET, }); } async validate(payload: any) { return { userId: payload.sub, email: payload.email }; } }
2. Implement the Dynamic Guard
Create a custom guard that uses Reflector to fetch the access right, maps it to the corresponding strategy name, and leverages NestJS's built-in AuthGuard integration:
// src/auth/guards/dynamic-jwt.guard.ts import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Reflector } from '@nestjs/core'; import { AccessRight } from '../access-right.enum'; @Injectable() export class DynamicJwtGuard extends AuthGuard() { constructor(private readonly reflector: Reflector) { super(); } // Dynamically select strategy based on decorator value protected getAuthenticateOptions(context: ExecutionContext) { const accessRight = this.reflector.get<AccessRight>( 'accessRight', // Matches metadata key from @SetAccessRight context.getHandler(), ); return { strategy: this.resolveStrategyName(accessRight), session: false, // Disable sessions for JWT auth }; } // Centralized error handling for all strategies protected handleRequest(err: any, user: any, info: any) { if (err || !user) { throw err || new UnauthorizedException(info?.message || 'Invalid or missing token'); } return user; } // Map AccessRight enum to strategy names private resolveStrategyName(accessRight?: AccessRight): string { switch (accessRight) { case AccessRight.refresh: return 'jwt-refresh'; case AccessRight.confirm: return 'jwt-confirm'; default: return 'jwt-access'; // Fallback to default access strategy } } }
3. Register the Guard Globally
Set the dynamic guard as your global authentication guard in the root module:
// src/app.module.ts import { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; import { DynamicJwtGuard } from './auth/guards/dynamic-jwt.guard'; import { JwtAccessStrategy } from './auth/strategies/jwt-access.strategy'; import { JwtRefreshStrategy } from './auth/strategies/jwt-refresh.strategy'; import { JwtConfirmStrategy } from './auth/strategies/jwt-confirm.strategy'; @Module({ providers: [ { provide: APP_GUARD, useClass: DynamicJwtGuard, }, JwtAccessStrategy, JwtRefreshStrategy, JwtConfirmStrategy, ], }) export class AppModule {}
4. Use the Decorator as Before
Your existing route decorator usage will now automatically switch the authentication strategy:
import { Get } from '@nestjs/common'; import { SetAccessRight } from './auth/access-right.decorator'; import { AccessRight } from './auth/access-right.enum'; @Get('refresh') @SetAccessRight(AccessRight.refresh) async refresh() { // Refresh token logic } @Get('confirm') @SetAccessRight(AccessRight.confirm) async confirmEmail() { // Email confirmation logic } // Defaults to jwt-access strategy @Get('profile') async getProfile() { // Profile fetch logic }
Key Fixes for Your Original Error
Calling a strategy's authenticate() method directly breaks Passport's internal context (like the error function binding). By extending AuthGuard, we reuse NestJS's pre-built integration with Passport, which handles request/response context and callback binding correctly.
内容的提问来源于stack exchange,提问作者Alex Green

