req.user if undefined in guards whileusing nest js + jwt + passport - node.js

I've implemented passport + jwt in my simple nestjs app for authentication. it's working fine. Now I want to use role-based authentication for routes but in my role.guard.ts , req.user is undefined. Please help me solve this issue.
auth.guard.ts
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
jwt.strategy.ts
import { Injectable } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { UserService } from 'src/user/user.service';
import { AuthService } from './auth.service';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
public authService: AuthService,
public configService: ConfigService,
public userService: UserService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('JWT_SECRET'),
// passReqToCallback: true,
// secretOrKey: authService.getConfig('JWT_SECRET'),
});
}
async validate(payload: any) {
const user = await this.userService.getOne(payload._id);
console.log('user', user);
return user;
// return { id: payload._id };
}
}
user.controller.ts
#Controller('users')
export class UserController {
constructor(private userService: UserService) {}
#Get('/')
#UseGuards(AuthGuard('jwt'))
#UseGuards(RolesGuard)
getAllUsers(): any {
return this.userService.getAll();
}
}
role.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import * as jwt from '#nestjs/jwt';
import { promisify } from 'util';
#Injectable()
export class RolesGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> {
const req = context.switchToHttp().getRequest();
console.log('req.user', req.user);
return true;
}
}

Don't use two #UseGuards(), use a single one and order the guards in the order you want them to execute:
#UseGuards(AuthGuard('jwt'), RolesGuard)

Related

using passport-jwt in nest with secretOrKeyProvider option keep responsing http 401?

I am a new coder with nestjs, I want to use passport-jwt , nestjs/passport and firebase to build my app's authetication part, below are my codes. But I just got http 401 response, how can i fix it ?
here is my strategy:
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { getAuth } from 'firebase-admin/auth';
#Injectable()
export class FirebaseStrategy extends PassportStrategy(Strategy, 'firebase') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKeyProvider: async (request, rawJwtToken, done) => {
try {
const decodedToken = await getAuth().verifyIdToken(rawJwtToken);
done(null, decodedToken);
} catch (error) {
done(error);
}
},
});
}
async validate(payload: any) {
console.log('validate');
return payload;
}
}
here is my guard:
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class FirebaseAuthGuard extends AuthGuard('firebase') {}
here is my auth.module.ts:
import { Module } from '#nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { PassportModule } from '#nestjs/passport';
import { JwtModule } from '#nestjs/jwt';
import { FirebaseStrategy } from './firebase.stragety';
import { AuthController } from './auth.controller';
#Module({
controllers: [AuthController],
imports: [UsersModule, PassportModule, JwtModule.register({})],
providers: [AuthService, FirebaseStrategy],
exports: [AuthService],
})
export class AuthModule {}
and here is my controller:
import { Controller, Get, Request, UseGuards } from '#nestjs/common';
import { AuthService } from './auth.service';
import { FirebaseAuthGuard } from './firebase.guard';
#Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
#UseGuards(FirebaseAuthGuard)
#Get('login')
async logIn(#Request() req) {
return 'login';
}
}
I just found my validate method in FirebaseStrategy not invoked, it should be invoked everytime when secretOrKeyProvider verified jwt in http header, isn't it ?
If you're getting a 401 with no call of the validate method of your JwtStrategy that means that for some reason passport is reading the JWT you send as invalid. To find the specific reason for it you can modify your FirebaseAuthGuard to have the following handleRequest method, which will log out extra details
handleRequest(err, user, info, context, status) {
console.log({ err, user, info, context, status });
return super.handleRequest(err, user, info, context, status);
}
As mentioned, this will print out what error or info passport is reading and throwing for, and will allow you to properly adjust your JWT/know to refresh it/change it because of invalid signature/whatever else.

nestjs jwt implementation is stuck - Nest can't resolve dependencies of the JWT_MODULE_OPTIONS (?)

I'm trying to implement JWT with NestJs. In my user.module.ts, I have added following configuration:
import { Module } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { UserService } from './user.service';
import { UserResolver } from './user.resolver';
import { User } from './entities/user.entity';
import { TypeOrmModule } from '#nestjs/typeorm';
import { AuthHelper } from './auth/auth.helper';
import { JwtStrategy } from './auth/auth.strategy';
import { PassportModule } from '#nestjs/passport';
import { JwtModule } from '#nestjs/jwt';
#Module({
imports: [
// PassportModule.register({ defaultStrategy: 'jwt', property: 'user' }),
// JwtModule.registerAsync({
// inject: [ConfigService],
// useFactory: (config: ConfigService) => ({
// secret: 'secret',
// signOptions: { expiresIn: 36000000 },
// }),
// }),
TypeOrmModule.forFeature([User]),
],
providers: [UserResolver, UserService], // AuthHelper, JwtStrategy],
})
export class UserModule {}
Whenever I uncomment these lines, I get some issues.
Here are some relevant files:
auth.strategy.ts
import { Injectable, Inject } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { User } from '../entities/user.entity';
import { AuthHelper } from './auth.helper';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
#Inject(AuthHelper)
private readonly helper: AuthHelper;
constructor(#Inject(ConfigService) config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'KhubSecret',
ignoreExpiration: true,
});
}
private validate(payload: string): Promise<User | never> {
return this.helper.validateUser(payload);
}
}
auth.guard.ts
import { Injectable, ExecutionContext } from '#nestjs/common';
import { AuthGuard, IAuthGuard } from '#nestjs/passport';
import { User } from '../entities/user.entity';
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') implements IAuthGuard {
public handleRequest(err: unknown, user: User): any {
return user;
}
public async canActivate(context: ExecutionContext): Promise<boolean> {
await super.canActivate(context);
const { user } = context.switchToHttp().getRequest();
return user ? true : false;
}
}
auth.helper.ts:
import {
Injectable,
HttpException,
HttpStatus,
UnauthorizedException,
} from '#nestjs/common';
import { JwtService } from '#nestjs/jwt';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcryptjs';
import { User } from '../entities/user.entity';
#Injectable()
export class AuthHelper {
#InjectRepository(User)
private readonly repository: Repository<User>;
private readonly jwt: JwtService;
constructor(jwt: JwtService) {
this.jwt = jwt;
}
public async decode(token: string): Promise<unknown> {
return this.jwt.decode(token, null);
}
public async validateUser(decoded: any): Promise<User> {
return this.repository.findOne(decoded.id);
}
public generateToken(user: User): string {
return this.jwt.sign({
id: user.userId,
username: user.username,
});
}
public isPasswordValid(password: string, userPassword: string): boolean {
return bcrypt.compareSync(password, userPassword);
}
public encodePassword(password: string): string {
const salt: string = bcrypt.genSaltSync(10);
return bcrypt.hashSync(password, salt);
}
private async validate(token: string): Promise<boolean | never> {
const decoded: unknown = this.jwt.verify(token);
if (!decoded) {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
const user: User = await this.validateUser(decoded);
if (!user) {
throw new UnauthorizedException();
}
return true;
}
}
I get some error like this:
[Nest] 18360 - 05/10/2022, 18:14:42 ERROR [ExceptionHandler] Nest can't resolve dependencies of the JWT_MODULE_OPTIONS (?). Please make sure that the argument ConfigService at index [0] is available in the JwtModule context.
Potential solutions:
- If ConfigService is a provider, is it part of the current JwtModule?
- If ConfigService is exported from a separate #Module, is that module imported within JwtModule?
#Module({
imports: [ /* the Module containing ConfigService */ ]
})
Error: Nest can't resolve dependencies of the JWT_MODULE_OPTIONS (?). Please make sure that the argument ConfigService at index [0] is available in the JwtModule context.
Potential solutions:
- If ConfigService is a provider, is it part of the current JwtModule?
- If ConfigService is exported from a separate #Module, is that module imported within JwtModule?
#Module({
imports: [ /* the Module containing ConfigService */ ]
})
I have already tested and implemented these solutions:
NestJS can't resolve dependencies of the JWT_MODULE_OPTIONS
Here is the solution. I hope it would help.
In your auth strategy add this:
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'KhubSecret',
ignoreExpiration: true,
});
}
instead of:
constructor(#Inject(ConfigService) config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'KhubSecret',
ignoreExpiration: true,
});
}
In Short, Remove
#Inject(ConfigService) config: ConfigService
from your constructor
From the immediate error you've provided, I'll assume that your ConfigModule is not global. This means that you'll need to import the ConfigModule in the JwtModule.registerAsync() call (in the imports array). Another option would be to make your ConfigModule global (#nestjs/config has an option for this) so that you already have access to ConfigService.

Using failureRedirect option of passport-local with Nest.js

I need help with processing after authentication using Nest.js
here do I pass the failureRedirect option for passport-local when using Nest.js for authentication?
Without Nest.js
app.post('/login', passport.authenticate('local', {
//Passing options here.
successRedirect: '/',
failureRedirect: '/login'
}));
My code is. (with Nest.js)
local.strategy.ts
import { Injectable, UnauthorizedException } from "#nestjs/common";
import { PassportStrategy } from "#nestjs/passport";
import { Strategy } from "passport-local";
import { AuthService } from "./auth.service";
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
//I tried passing the option here. but failed.
})
}
async validate(username: string, password: string): Promise<string | null> {
const user = this.authService.validate(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
local.guard.ts
import { Injectable } from "#nestjs/common";
import { AuthGuard } from "#nestjs/passport";
#Injectable
export class LocalAuthGuard extends AuthGuard('local') {}
auth.controller.ts
import { Controller, Get, Post, Render, UseGuards } from "#nestjs/common";
import { LocalAuthGuard } from "./local.guard";
#Controller()
export class AuthController {
#Get("/login")
#Render("login")
getLogin() {}
//Redirect to '/login' when authentication failed.
#UseGuards(LocalAuthGuard)
#Post("/login")
postLogin() {}
}
auth.module.ts
import { Module } from "#nestjs/common";
import { PassportModule } from "#nestjs/passport";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { LocalStrategy } from "./local.strategy";
import { LocalAuthGuard } from "./local.guard";
#Module({
controllers: [AuthController],
imports: [PassportModule],
providers: [AuthService, LocalStrategy, LocalAuthGuard]
})
export class AuthModule {}
I tried adding code to AuthController#postLogin to redirect on login failure, but the code seems to run only on successful login.
I would like to redirect to the login page again in case of login failure with the failureRedirect option of passport-local.
I found a workaround since using the passport options sadly didn't work:
#Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
getAuthenticateOptions(context: ExecutionContext): IAuthModuleOptions {
return {
successReturnToOrRedirect: '/',
failureRedirect: '/login',
};
}
}
Instead I created a Nestjs Filter to catch an exception containing a redirect URL.
redirecting.exception.ts
export class RedirectingException {
constructor(public url: string) {}
}
redirecting-exception.filter.ts
import { ArgumentsHost, Catch, ExceptionFilter } from '#nestjs/common';
import { Response } from 'express';
import { RedirectingException } from './redirecting.exception';
#Catch(RedirectingException)
export class RedirectingExceptionFilter implements ExceptionFilter {
catch(exception: RedirectingException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
response.redirect(exception.url);
}
}
In my validate method I'm throwing the RedirectingException with the correct error msg, e.g.
throw new RedirectingException('/login?error="User not found"');
And the controller handles the rest of the redirecting and passes the error to the view, so it can be displayed:
#Get('/login')
#Render('login.pug')
#Public()
async login(#Query() query) {
return { error: query.error };
}
#Post('/login')
#Public()
#UseGuards(LocalAuthGuard)
#Redirect('/')
async doLogin() {}
I'd rather use the passport functionality including the failureFlash, but I couldn't get it to work.

Nest.JS - Global authentication guard

I want to implement a global authentication guard in My NestJS application that will simply read certain headers and assign user values based on those headers, for every request that comes in.
I implemented this simple logic and registered my global guard in my main module, however for some reason all my requests fail with '401 Unauthorized'. I tried to place log messages inside internal.strategy.ts, however I don't see them getting called.
Any idea why the strategy is not called?
This is my main.ts:
import { NestFactory, Reflector } from '#nestjs/core';
import * as logging from './logging';
import { AppModule } from './app.module';
import config from './config';
import { LocalAuthGuard } from './auth/guards/local-auth.guard';
async function bootstrap(port: string | number) {
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new LocalAuthGuard())
await app.listen(port, '0.0.0.0');
logging.logger.info(`Listening on 0.0.0.0:${port}`);
}
bootstrap(config.port);
This is my auth.module.ts:
import { Module } from '#nestjs/common';
import { PassportModule } from '#nestjs/passport';
import { AuthService } from './auth.service';
import { InternalStrategy } from './stategies/internal.strategy';
#Module({
imports: [PassportModule],
providers: [AuthService, InternalStrategy ]
})
export class AuthModule {}
This is my auth.service.ts:
import { Injectable } from '#nestjs/common';
import { Role } from 'src/workspaces/interfaces/models';
#Injectable()
export class AuthService {
validateUser(headers: Headers): any {
const workspaceId = headers['workspace-id'];
const workspaceRole = Role[headers['workspace-role']];
return {
workspaceId: workspaceId,
workspaceRole: workspaceRole
}
}
}
This is my internal.strategy.ts:
import { Strategy } from 'passport-local';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { AuthService } from '../auth.service';
#Injectable()
export class InternalStrategy extends PassportStrategy(Strategy, 'internal') {
constructor(private authService: AuthService) {
super({ passReqToCallback: true });
}
async validate(req: Request): Promise<any> {
console.log('Validate internal strategy')
const user = await this.authService.validateUser(req.headers);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
Here is my local-auth.guard.ts:
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class LocalAuthGuard extends AuthGuard('internal') {}

Authentication & Roles with Guards/Decorators: How to pass user object?

With the help of Guards/Decorators I try to check a JWT first and then the roles a user has.
I have read the documentation regarding Authentication, Guards and Decorators and understand the principles behind them.
However, what I cannot do is to somehow make the authenticated user from JWT-Guard available to Roles-Guards.
In every example that I found, exactly this part that is interesting for me is skipped / left out...
Grateful for every tip!
This is my latest try:
jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { JwtPayload } from './jwt.model';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
passReqToCallback: true,
ignoreExpiration: false,
secretOrKey: '0000',
expiresIn: '3 days'
});
}
async validate(payload: JwtPayload) {
return {
id: payload.id,
email: payload.email,
username: payload.username
};
}
}
roles.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {
}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return false;
}
const request = context.switchToHttp().getRequest();
const user = request.user ??? // THIS is what is missing
return roles.some((role) => {
return role === user.role;
});
}
}
roles.decorator.ts
import { SetMetadata } from '#nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
users.controller.ts
#UseGuards(AuthGuard('jwt'))
#Roles('admin', 'member')
#Get('/')
async doSomething(#Req() req): Promise<User> {
return await this.usersService.doSomething(req.user.id);
}
Your decorator and guards look fine, but from the snippet of your users.controller.ts file it is not clear whether the roles guard is actually applied for the GET / route.
I do, however, have an NestJS app with a quite similar setup based on the guards documentation. The following code in users.controller.ts works as intended:
#UseGuards(JwtAuthGuard, RolesGuard)
#Controller('/users')
export class UserController {
constructor(private readonly userService: UserService) {}
#Get()
#Roles(UserRole.ADMIN)
public async index(): Promise<User[]> {
return this.userService.findAll();
}
// ...
}
Note how both the auth and roles guard are activated in the same scope and that JwtAuthGuard is added before RolesGuard. If I were to change the sequence of the guards then the RolesGuard would not be able to retrieve the user of the request.
Also, you might want to have a look at a similar question from some time ago which contains some details on the order of guards in different scopes.

Resources