微服务API网关与认证服务设计优化及Passport适配TCP问题
Hey there! Let's break down your questions step by step, starting with design sanity, then moving to better approaches, and finally solving the Passport-TCP adaptation problem.
Is Your Current Design Reasonable?
Short answer: Yes, for the most part.
Splitting your system into an API Gateway (routing/request orchestration) and a dedicated Auth Service (credential/token validation) follows solid microservice principles—single responsibility, separation of concerns. Using TCP for inter-service communication is also a good call for low-latency, efficient microservice interactions.
The pain point you're hitting (Passport strategies not working with TCP requests) isn't a flaw in your core design—it's just that Passport is inherently built around HTTP request/response cycles. It relies on extracting credentials from HTTP-specific sources (like request headers), which don't exist in plain TCP messages. So the issue is coupling your authentication logic to Passport's HTTP-centric utilities, not your overall service architecture.
Better Design Approaches
To fix the code reuse problem and make your system more flexible, here are two key optimizations:
1. Decouple Authentication Logic from Passport
Passport is great for handling HTTP auth flows, but your core validation logic (checking password matches, verifying JWT payloads) shouldn't be tied to it. Extract that logic into standalone service classes that can be called from both Passport strategies and TCP message handlers.
2. Keep Auth Service Focused, But Flexible
Your Auth Service can still use TCP for inter-service calls, but by separating the validation logic from Passport, you can reuse the same code regardless of whether the request comes via HTTP (for direct client auth, if needed) or TCP (from the API Gateway).
Adapting Passport Strategies to TCP Requests
Here's how to refactor your existing code to reuse the Passport-backed logic in your TCP-based Auth Service:
Step 1: Extract Core Validation Logic into Services
First, pull the validation logic out of your Passport strategies into standalone injectable services. This is the key to reuse.
For Local (Email/Password) Auth:
@Injectable() export class LocalAuthService { constructor(private readonly userService: UserService) {} async validateUser(email: string, password: string): Promise<UserLoginReqDTO> { const user = await this.userService.getUserIfPasswordMatches(email, password); if (!user) { throw new UnauthorizedException(); } return user; } }
For JWT Access Token Validation:
import * as jwt from 'jsonwebtoken'; @Injectable() export class JwtAuthService { constructor( private readonly tokenService: TokenService, private readonly configService: ConfigService ) {} async validateAccessToken(token: string): Promise<UserSimpleDTO> { try { // Verify the token signature and decode payload const payload = jwt.verify(token, this.configService.get('JWT_ACCESS_SECRET')) as TJwtPayload; // Validate the payload against your user data const user = await this.tokenService.validatePayload(payload); if (!user) { throw new UnauthorizedException('Cannot find user via payload'); } return user; } catch (error) { // Handle invalid/expired tokens throw new UnauthorizedException('Invalid or expired access token'); } } // Add this method to reuse payload validation in Passport strategy async validatePayload(payload: TJwtPayload): Promise<UserSimpleDTO> { const user = await this.tokenService.validatePayload(payload); if (!user) { throw new UnauthorizedException('Cannot find user via payload'); } return user; } }
Step 2: Update Passport Strategies to Reuse These Services
Now your Passport strategies can become thin wrappers around the core validation services, keeping them HTTP-focused but reusing the actual logic:
Updated LocalStrategy:
import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-local'; import { Injectable } from '@nestjs/common'; import { LocalAuthService } from './local-auth.service'; import { UserLoginReqDTO } from '@modules/user/dto'; @Injectable() export class LocalStrategy extends PassportStrategy(Strategy) { constructor(private readonly localAuthService: LocalAuthService) { super({ usernameField: 'email' }); } async validate(email: string, password: string): Promise<UserLoginReqDTO> { // Delegate to the reusable service return this.localAuthService.validateUser(email, password); } }
Updated JwtAccessTokenStrategy:
import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { Injectable } from '@nestjs/common'; import { TJwtPayload } from '../../types/auth/jwt-payload.type'; import { UserSimpleDTO } from '@modules/user/dto'; import { ConfigService } from '@nestjs/config' import { JwtAuthService } from './jwt-auth.service'; @Injectable() export class JwtAccessTokenStrategy extends PassportStrategy(Strategy, 'jwt-access-token') { constructor( private readonly jwtAuthService: JwtAuthService, configService: ConfigService ) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: configService.get("JWT_ACCESS_SECRET"), }); } async validate(payload: TJwtPayload): Promise<UserSimpleDTO> { // Reuse the payload validation logic from the shared service return this.jwtAuthService.validatePayload(payload); } }
Step 3: Use the Same Services in Your TCP Message Handlers
Now your TCP endpoints can call the exact same validation services, eliminating code duplication:
import { Controller } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; import { LocalAuthService } from './local-auth.service'; import { JwtAuthService } from './jwt-auth.service'; import { LoginUserResponseDto } from './dto/login-user-response.dto'; // Import your token generation service here @Controller() export class AuthTCPController { constructor( private readonly localAuthService: LocalAuthService, private readonly jwtAuthService: JwtAuthService, private readonly tokenGenerationService: TokenGenerationService // Example token service ) {} @MessagePattern('login') async login(loginRequest: { email: string; password: string }): Promise<LoginUserResponseDto> { // Reuse local auth validation logic const user = await this.localAuthService.validateUser(loginRequest.email, loginRequest.password); // Generate access/refresh tokens with your existing logic const { access_token, refresh_token } = await this.tokenGenerationService.generateTokens(user); return { username: user.username, access_token, refresh_token }; } @MessagePattern('verify_access_token') async verifyAccessToken(data: { token: string }) { // Reuse JWT validation logic return this.jwtAuthService.validateAccessToken(data.token); } }
Key Takeaways
- Your core service architecture is sound—keep the API Gateway and Auth Service separation.
- The fix for code reuse is decoupling validation logic from Passport's HTTP-specific tools.
- By extracting validation into standalone services, you can reuse the same code across both HTTP (via Passport) and TCP (via microservice messages) contexts.
内容的提问来源于stack exchange,提问作者ThuanNguyen

