import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-local'; import { lastValueFrom } from 'rxjs'; import { AuthService } from '../auth.service'; import { UserPrincipal } from '../interface/user-principal.interface'; @Injectable() export class LocalStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super({ usernameField: 'username', passwordField: 'password', }); } // When using Observable as return type, the exeption in the pipeline is ignored. // In our case, the `UnauthorizedException` is **NOT** caught and handled as expected. // The flow is NOT prevented by the exception and continue to send a `Observable` to // the next step aka calling `this.authService.login` in `AppController#login` method. // Then the jwt token is generated in any case(eg. wrong username or wrong password), // the authenticatoin worflow does not work as expected. // // The solution is customizing `PassportSerializer`. // Example: https://github.com/jmcdo29/zeldaPlay/blob/master/apps/api/src/app/auth/session.serializer.ts // // validate(username: string, password: string): Observable<any> { // return this.authService // .validateUser(username, password) // .pipe(throwIfEmpty(() => new UnauthorizedException())); // } async validate(username: string, password: string): Promise<UserPrincipal> { const user: UserPrincipal = await lastValueFrom( this.authService.validateUser(username, password), ); if (!user) { throw new UnauthorizedException(); } return user; } }