Consider that I have two routes in NestJS, called "signUp" and "validate-code", both of which are inside the SignupModule.
For the signUp route, I return an accessToken using JWT. Here's the code:
const payload = {
mobileNumber: user.mobileNumber
};
return [
{...userData,accessToken: this.jwtService.sign(payload),}
];
After sending the mobile number as a request using Postman to the server, everything works fine and I get an accessToken.
Now, I want to use this accessToken to authorize the validate-code route and check if the user has already registered and their information exists in the database. I have to send the mobileNumber and code to this route along with the accessToken that I received previously through the signUp route. I set the accessToken in Postman as Bearer, but it gives me the following message:
{
"statusCode": 401,
"message": "Unauthorized"
}
The classes written for JWT are as follows:
import {Injectable} from "@nestjs/common";
import {AuthGuard} from "@nestjs/passport";
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
}
//---------------------------------------------------------------
import {AuthGuard} from '@nestjs/passport';
import {Injectable} from "@nestjs/common";
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
}
//---------------------------------------------------------------
import {PassportStrategy} from "@nestjs/passport";
import {ExtractJwt} from "passport-jwt";
import {Strategy} from "passport-local";
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: `${process.env.JWT_SECRET}`,
ignoreExpiration: false,
passReqToCallback: true,
});
}
async validate(payload: any) {
return {
mobileNumber: payload.mobileNumber
};
}
}
//---------------------------------------------------------------
import {Injectable, UnauthroziedException} from "@nestjs/common";
import {PassportStrategy} from "@nestjs/passport";
import {Strategy} from 'passport-local';
import {SignupService} from "./signup.service";
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly signupService: SignupService) {
super();
}
async validate(mobileNumber: string) {
const user = await this.signupService.validateUser(mobileNumber);
if (!user) {
throw new UnauthroziedException();
}
return user;
}
}
The signUp route in the SignupController:
@Post('signup')
async signUp(@Body() dto: SignupDto, @Res() res: Response):Promise<Response<any,Record<string, any>>> {
const result = await this.authService.signUp(dto.mobileNumber);
return res.status(result ? HttpStatus.CREATED : HttpStatus.INTERNAL_SERVER_ERROR).json({'message':result});
}
The validate-code route in the SignupController:
@UseGuards(LocalAuthGuard)
@Post('validate-code')
async validateCode(@Body() dto:VerifyCodeDto,@Res() res:Response):Promise<Response<any,Record<string, any>>>{
const [statusCode,data] = await this.authService.verifyCode(dto);
return res.status(statusCode).json(data);
}
Finally, the SignupModule is implemented to use JWT and Passport:
@Module({
imports: [
JwtModule.register({
secret: `${process.env.JWT_SECRET}`,
signOptions: { expiresIn: '60s' },
}),
ThrottlerModule.forRoot(
{
ttl: 30,
limit: 6
}
),
PrismaModule,
ApiModule
],
controllers: [AuthController],
providers: [
JwtStrategy,
LocalStrategy,
SignupService,
{
provide: APP_GUARD,
useClass: ThrottlerGuard
}
]
})
export class SignupModule {
}
So, I have no problem with the signUp route and getting the accessToken, but I cannot use it for the validate-code route. What could be the problem?
This can happen for two reasons:
validate
function didn't run at all: this may occur when the token is not valid or already expired, you have to verify that process.env.JWT_SECRET
is properly passed to secret
and secretOrKey
. also make sure the token created is encrypted with the same string. if the token is not valid or expired then the validate
function will not run and you get Unauthorized
as message
value in the response.validate
function is running but this.signupService.validateUser(mobileNumber)
is returning a "falsy" result so user
is not defined therefore new UnauthroziedException()
is thrown.Note: I usually work with only one strategy but here you are using two
the first one:
export class JwtAuthGuard extends AuthGuard('jwt')
//...
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt')
the second one:
export class LocalAuthGuard extends AuthGuard('local')
//...
export class LocalStrategy extends PassportStrategy(Strategy)
then you add @UseGuards()
with LocalAuthGuard
to your @Post('validate-code')
route but I can't see from the code any indication that LocalAuthGuard
should use LocalStrategy
as strategy, make sure the expected validate
function is running.
from Nestjs docs about named strategies:
When implementing a strategy, you can provide a name for it by passing a second argument to the PassportStrategy function. If you don't do this, each strategy will have a default name (e.g., 'jwt' for jwt-strategy):
export class JwtStrategy extends PassportStrategy(Strategy, 'myjwt')
Then, you refer to this via a decorator like
@UseGuards(AuthGuard('myjwt'))