8

I'm trying to implement a Passport Local Strategy but the validate method is not working. When I do @UseGuards(AuthGuard("local")), it automatically throws an Unauthorized Exception without going through the validate method that I wrote. I have no idea what I'm doing wrong as the documentation did the same.

Here's my LocalStrategy class:

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(
    @InjectRepository(UserRepository) private userRepository: UserRepository,
  ) {
    super();
  }

  async validate(credentials: string, password: string): Promise<User> {
    // this method is never called, I've already did some console.logs
    const user = await this.userRepository.findByCredentials(credentials);

    if (!user) throw new UnauthorizedException('Invalid credentials');

    if (!(await argon2.verify(user.hash, password)))
      throw new UnauthorizedException('Invalid credentials');

    return user;
  }
}

My AuthModule imports:

@Module({
  imports: [TypeOrmModule.forFeature([UserRepository]), PassportModule],
  controllers: [AuthController],
  providers: [AuthService, LocalStrategy],
})
export class AuthModule {}

Example usage:

  @Post("/login")
  @UseGuards(LocalAuthGuard)
  async login(@Body() loginDto: LoginDto) {
    return this.authService.login(loginDto);
  }
Artyom Ionash
  • 405
  • 7
  • 17
Skull Cutter
  • 81
  • 1
  • 3
  • 1
    what's the documentation link? – sid Jun 29 '21 at 03:01
  • https://docs.nestjs.com/security/authentication – Skull Cutter Jun 29 '21 at 03:29
  • this may put you in right direction https://stackoverflow.com/questions/62799708/nest-js-auth-guard-jwt-authentication-constantly-returns-401-unauthorized – sid Jun 29 '21 at 03:32
  • Ohhh I think nest expects a field called username and not credentials. If I want to call it credentials I need to pass in options yo super. Thanks! – Skull Cutter Jun 29 '21 at 03:45
  • 1
    if that solves, please post an answer it would be helpful for others in the future – sid Jun 29 '21 at 04:03
  • Yes that solves the thing. Thanks a lot @sid – Skull Cutter Jun 29 '21 at 08:02
  • @SkullCutter how do you solved your problem ? I already read the answer mentioned before, but I still have the same problem : https://stackoverflow.com/questions/70896560/nestjs-and-passport-local-authguardlocal-validate-never-get-called – Gigs Jan 28 '22 at 16:06

3 Answers3

25

EDIT

After spending more time with the code and doing a deep dive myself, it's not the fact that the validate method must have parameters named username and password, they could be bob and alice for all that matters, but what is important is that your req.body has two properties username and password. If you do not have req.body.username and req.body.password, then you will never make it to the validate of the LocalStrategy class.


The validate method must have the parameters username and password or the parameters must match the usernameField and passwordField values passed to super() in the constructor. If they do not match, the validate method will not be called. I think this comes from the fact that Nest calls validate(...args), but am not 100% certain.

Jay McDoniel
  • 57,339
  • 7
  • 135
  • 147
  • worked. Thanks :) – aatif shaikh Apr 19 '22 at 02:06
  • 1
    I lost so much time onto this! should exists and log or something to tell that the validate will not be called because the fields did not match – Renato Cron Aug 12 '22 at 14:21
  • 2
    You actually can configure the fields names on the Local Strategy to be what you desire. This can be done by calling the super() and passing an options mapping object. We can pass an options object. This will customize the behavior of the passport strategy. In this example, the passport-local strategy by default expects properties called username and password in the request body. Pass an options object to specify different property names, for example: super({ usernameField: 'email' }). See the Passport documentation for more information. – Yaron Miro Sep 26 '22 at 06:52
  • This should be marked as the accepted answer. It nicely expatiates on [an obscure part of the NestJs documentation](https://docs.nestjs.com/security/authentication#:~:text=We%20can%20pass%20an%20options%20object%20in%20the%20call%20to%20super()%20to%20customize%20the%20behavior%20of%20the%20passport%20strategy.%20In%20this%20example%2C%20the%20passport%2Dlocal%20strategy%20by%20default%20expects%20properties%20called%20username%20and%20password%20in%20the%20request%20body). – Benny64 Jan 14 '23 at 10:08
0

You can also modify the parameters that the validate function should use by adding the following:

It is important to add the payload object

async validate(payload: {email: string, UserId: number, role: string}) {
return { userId: payload.UserId, email:payload.email, role:payload.role };

}

I sign the token this way:

async signToken(userId: number, email: string, role: string): Promise<{access_token:string}>{
        const payload = {
            UserId: userId,
            email: email,
            role: role
        }

        console.log(payload)

        const secret = process.env.JWT_SECRET

        const token = await this.JwtService.signAsync(payload, {
            expiresIn: "1d",
            secret: secret
        })
benni0108
  • 75
  • 2
  • 8
-1

Seems you're missing the LocalAuthGuard class here. You need to create a file with this class and then..

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

This will then use the local guard.

sid
  • 1,779
  • 8
  • 10