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
.
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.
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
.