authenticationjwtnestjsbearer-tokenpassport-jwt

Auth Guard with JWT tokens in NestJS return 401 Unauthorized


I'm implementing JWT authentication and Role-Based Authorization in NestJS following this article

When I signup and login, it works I get the access_token but when I try to use the token for Authentication I always get 401 Unauthorized.

Login on Postman .png

User as Admin authentication on Postman .png

Here is my code:

app.controller

import { Body, Controller, Get, Post, Request, UseGuards } from '@nestjs/common';
import { AuthService } from './auth/auth.service';
import { SignUpDTO } from './auth/dto/signup.dto';
import { HasRoles } from './auth/roles/has-roles.decorator';
import { Role } from './auth/roles/role.enum';
import { RoleGuard } from './auth/roles/role.guard';
import { JwtAuthGuard } from './auth/strategy/jwt-auth.guard';
import { LocalAuthGuard } from './auth/strategy/local-auth.guard';
import { User } from './user/interfaces/user.interface';

@Controller()
export class AppController {
constructor(private authService: AuthService) {}

@Post('auth/register')
async register(@Body() signUpDTO :SignUpDTO): Promise\<User\> {
return this.authService.signUp(signUpDTO)
}
@UseGuards(LocalAuthGuard)
@Post('auth/login')
async login(@Request() req) {
return this.authService.login(req.user);
}

@UseGuards(JwtAuthGuard)
@Get('profile')
getAuthJWT(@Request() req) {
return req.user;
}

@HasRoles(Role.USER, Role.SELLER, Role.BUYER)
@UseGuards(JwtAuthGuard, RoleGuard)
// @UseGuards(JwtAuthGuard)
@Get('user')
getProfile(@Request() req) {
return {data: req.user, messgage: `Welcome ${req.user.roles}`};
}

@HasRoles(Role.ADMIN)
@UseGuards(JwtAuthGuard, RoleGuard)
@Get('admin')
async adminOnlyEndpoint(@Request() req) {
return {data: req.user, messgage: "Welcome admin"};
}
}

auth.module

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { UserModule } from 'src/user/user.module';
import { AuthService } from './auth.service';
import { JwtStrategy } from './strategy/jwt.strategy';
import { LocalStrategy } from './strategy/local.strategy';

@Module({
  imports: [
    UserModule,
    PassportModule,
    JwtModule.register({
      global: true,
      secret: `${process.env.JWT_SECRET_KEY}`, 
      signOptions: { expiresIn: '10m'},
    }),
  ],
  providers: [
    AuthService,
  LocalStrategy,
    JwtStrategy,
  ],
  exports: [AuthService]
})
export class AuthModule {}

auth.service

import { Injectable, NotAcceptableException, UnauthorizedException } from '@nestjs/common';
import { User } from 'src/user/interfaces/user.interface';
import { UserService } from 'src/user/user.service';
import { hashPassword } from 'src/_utils/hashPassword';
import { validatePassword } from 'src/_utils/validatePassword';
import { SignUpDTO } from './dto/signup.dto';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {

    constructor(
    private userService: UserService,
        private jwtService: JwtService,
    ) { }

    async login(user: any) {
        const payload = {
            _id: user._id,
            username: user.username,
            roles: user.roles,
        };
        return {
            access_token: this.jwtService.sign(payload),
        };
    }

    async validateUser(username: string, password: string): Promise<User | null> {
        const user = await this.userService.getByUsername(username);
        if (!user) return null;
        const passwordValid = await validatePassword(password, user.password)
        if (!user) {
            throw new NotAcceptableException('could not find the user');
        }
        if (user && passwordValid) {
            return user;
        }
        return null;
    }

    async signUp(signupUserDTO: SignUpDTO): Promise<User> {
        const emailExisting = await this.userService.getByEmail(signupUserDTO.email)
        const usernameExisting = await this.userService.getByUsername(signupUserDTO.username)
        const hashedPassword = await hashPassword(signupUserDTO.password);
        if (emailExisting) {
            throw new UnauthorizedException(`This email's already existed`);
        } else if (usernameExisting) {
            throw new UnauthorizedException(`This username's already existed`);
        } else return await this.userService.create({ ...signupUserDTO, password: hashedPassword })
    }

}

local.strategy

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from '../auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super();
  }

  async validate(username: string, password: string): Promise<any> {
    const user = await this.authService.validateUser(username, password);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }

}

jwt.strategy

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.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: `${process.env.JWT_SECRET_KEY}`,
    });
  }
  
  async validate(payload: any) {
    return {
      _id: payload._id,
      username: payload.username,
      roles: payload.roles,
    };
  }

}

My backend project used to work perfectly a few weeks ago but currently it doesn't work anymore. I have no idea what's wrong with this code because I haven't made any changes since then.

If you need more information, please let me know.

Thank you in advance.


Solution

  • The question has been resolved. I have discovered the cause of the problem.

    Although I used the same method to import the JWT secret key into both my auth.module.ts and jwt.strategy.ts files using ${process.env.JWT_SECRET_KEY}, the values were not equal. As a result, my JWTGuard was unable to properly extract the token, which led to a 401 Unauthorized response."

    To access values in the .env file, I use the ConfigService which has a .get() method. To import the JWT module, I use the Factory function technique and pass an async config instead of a normal one at auth.module.ts. And I also inject the ConfigService into the JWTStrategy at jwt.strategy.ts.