Get current user in nestjs on a route without an AuthGuard - node.js

I use nestjs with passport with jwt strategy. And I want to get a current user on some of my requests.
Currently, I have a decorator that looks like this:
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const CurrentUser = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const user = ctx.switchToHttp().getRequest().user;
if (!user) {
return null;
}
return data ? user[data] : user; // extract a specific property only if specified or get a user object
},
);
It works as intended when i use it on a route with an AuthGuard:
#Get('test')
#UseGuards(AuthGuard())
testRoute(#CurrentUser() user: User) {
console.log('Current User: ', user);
return { user };
}
But how do i make it work (get current user) on non-guarded routes? I need users to be able to post their comments regardless of if they are authorized or not, however, when they are logged in, i need to get their name.
Basically, I need a way to propagate req.user on every(or at least on some of not AuthGuard'ed request), it is really straight forward to do in express by applying passport middleware, but I'm not sure how to do it with #nestjs/passport.
[EDIT]
Thanks to vpdiongzon for pointing me in the right direction, I chose to make a guard based on his answer, that just populates req.user with either user or null:
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class ApplyUser extends AuthGuard('jwt') {
handleRequest(err: any, user: any) {
if (user) return user;
return null;
}
}
And now I could just use it on any unprotected route that needs to get the current user
#Get('me')
#UseGuards(ApplyUser)
me(#CurrentUser() user: User) {
return { user };
}

You need to apply your AuthGuard to every route regardless but if you have a route that don't require authentication just add a custom decorator, example:
the Auth Guard
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private readonly reflector: Reflector) {
super();
}
handleRequest(err, user, info, context) {
const request = context.switchToHttp().getRequest();
const allowAny = this.reflector.get<string[]>('allow-any', context.getHandler());
if (user) return user;
if (allowAny) return true;
throw new UnauthorizedException();
}
}
Apply globally the AuthGuard in app.module.js
import { APP_GUARD, Reflector } from '#nestjs/core';
import { AppController } from './app.controller';
import { JwtAuthGuard } from './app.guard';
#Module({
imports: ],
controllers: [AppController],
providers: [
{
provide: APP_GUARD,
useFactory: ref => new JwtAuthGuard(ref),
inject: [Reflector],
},
AppService,
],
})
export class AppModule {
}
The custom decorator to allow a route without authentication
import { SetMetadata } from '#nestjs/common';
export const AllowAny = () => SetMetadata('allow-any', true);
Apply AllowAny in a route, if AllowAny decorator is not attached in a controller route it will required a user.
#Post('testPost')
#AllowAny()
async testPost(#Req() request) {
console.log(request.user)
}

"Basically, I need a way to propagate req.user on every(or at least on some of not AuthGuard'ed request), it is realy straight forward to do in express by applying passport middleware, but im not sure how to do it with #nestjs/passport."
To achieve this we write an interceptor because we need to use the UsersService. UserService is part of the dependency injection system. We cannot just import the user service and create a new instance of it ourselves. The service makes use of the users repository and that users repository is setup only through dependency injection.
The thing is we cannot make use of dependency injection with a parameter decorator. This decorator cannot reach into the system in any way and try to get access to some instance of anything inside there. This is how we write the interceptor. I make comments on the code:
// this interceptor will be used by the custom param decoratro to fetch the current User
import {NestInterceptor,ExecutionContext,CallHandler,Injectable} from '#nestjs/common';
import { UsersService } from '../users.service';
#Injectable()
// "implements" guide us how to put together an interceptor
export class CurrentUserInterceptor implements NestInterceptor {
constructor(private userService: UsersService) {}
// handler refers to the route handler
async intercept(context: ExecutionContext, handler: CallHandler) {
const request = context.switchToHttp().getRequest();
const { userId } = request.session || {};
if (userId) {
const user = await this.userService.findOne(userId);
// we need to pass this down to the decorator. SO we assign the user to request because req can be retrieved inside the decorator
// ------THIS IS WHAT YOU WANTED--------
request.currentUser = user;
}
// run the actual route handler
return handler.handle();
}
}
Now you need to register this to the module:
#Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService, AuthService, CurrentUserInterceptor],
})
Inside controller:
#Controller('auth')
#UseInterceptors(CurrentUserInterceptor)
export class UsersController {
constructor("inject services) {}
#Get('/me')
me(#CurrentUser() user: User) {
return user;
}
}
In any route handler you use CurrentUser param decorator, you will have access to "user".
You actually do not need to write a custom param decorator
you could just use the interceptor, its implementation would be different:
#Get('/me')
me(#CurrentUserInterceptor() request: Request) {
// You have access to request.currentUser
return request.currentUser
}
Set interceptor globally
The current setup for the interceptor is tedious. We are applying the interceptor to one controller at a time. (Thats called controlled scope) Instead you could globally make this interceptor available:
users Module:
import { APP_INTERCEPTOR } from '#nestjs/core';
#Module({
// this createes repository
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [
UsersService,
AuthService,
{
provide: APP_INTERCEPTOR,
useClass: CurrentUserInterceptor,
},
],
})
This approach has one downside. Not every controller cares about what the current user is. In those controllers, you still have to make request to the database to fetch the current User.

the parsed userinfo stored in request.user
import {Req} from '#nestjs/common'
import { Request } from 'express'
#Post()
create(#Req() request: Request) {
console.log('user', request.user)
}

Related

How to excecute guard before injected provider into Scope.Request

I am working on a multi-tenant app using NestJS and I store the tenantId in the token using Jwt, I need to create a database tenant connection before I do database operations but the provider(code below) is being executed before the JwtAuthGuard but I need the guard to be executed first, Is there a way to change the order of execution?
Controller method (uses JwtAuthGuard):
#Post()
#UsePipes(new ValidationPipe())
#UseGuards(JwtAuthGuard)
create(#Body() createUserDto: CreateFruitDto) {
return this.fruitsService.create(createUserDto);
}
Passport strategy (JwtAuthGuard):
export class JwtStrategy extends PassportStrategy(Strategy) {
private logger = new Logger('JwtStrategy');
constructor(private configService: JwtConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: configService.ignoreExpiration,
secretOrKey: configService.options.secret,
});
}
async validate(payload: any) {
//injects user into req
return {
userId: payload.sub,
email: payload.email,
tenantId: payload.tenant,
};
}
}
Provider being injected into FruitsModule:
provide: 'TENANT_CONTEXT',
scope: Scope.REQUEST,
inject: [REQUEST],
useFactory: (req: Request): ITenantContext => {
const { user } = req as any;
Logger.log(user); // is undefined
const tenantContext: ITenantContext = {
user.tenantId,
};
return tenantContext;
},
IMHO best to avoid request scoped providers. That should have never been introduced in Nest. That scope bubbles up and makes everything above it request scoped as well.
You could introduce middleware to work around this. Middlewares are executed before guards. The auth guard validates and extracts data from the JWT token and stores it on req.user. Configure a middleware to prepare a user property on the request. Its setter will be executed when the auth guard sets the user property on the request and it will extract the tenant ID for you.
interface ExecutionMetadata {
tenantId?: number;
}
export class TenantContextMiddleware implements NestMiddleware {
public async use(req: Request, res: Response, next: NextFunction) {
this.metadata: ExecutionMetadata = { tenantId: req.user?.tenantId };
Object.definePropery(req, 'user', {
set(user) {
this._user = user;
this.metadata.tenantId = user?.tenantId;
},
get() {
return this._user;
}
});
next();
}
}
Here I extract the tenant ID from the req.user and store it on the req.metadata property.
Using the createParamdecorator() function from NestJS you could then write a simple parameter decorator to inject this metadata.
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const Metadata = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.metadata;
},
);
You can then use this decorator to inject this metadata into your controller.
#Controller('cats')
export class CatsController {
#Get()
findAll(#Metadata() metadata: ExecutionMedata): string {
...
}
}
Remark: This decorator will only work for controller methods! NestJS is able to resolve the value for you at that stage of the request. Similar to the #Body(), #Param(), #Query()...decorators. Then you can pass this metadata down as an argument. Or you could do something fancy and setup asynchronous context tracking.

PassportJS, NestJS: canActivate method of AuthGuard('jwt')

Does anybody know where I can see the full code of canActivate method in AuthGuard('jwt')? I realized that canActivate method calls JwtStrategy validate method by using console.log() like this:
// jwt.strategy.ts
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly configService: ConfigService,
private readonly usersService: UsersService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: true,
secretOrKey: configService.get<string>('JWT_SECRET'),
});
}
async validate(payload: any) {
try {
const user = await this.usersService.getUserById(payload.id);
// console.log is here
console.log(user);
return user;
} catch (e) {
console.log(e);
return null;
}
}
}
If I use the original canActivate method, console.log is called. I thought that JwtStrategy is a middleware so the validate method is called whenever there is a request. However, when I try to override canActivate method to add authorization, console.log in JwtStrategy validate method is not called:
// jwt-auth.guard.ts
import { ExecutionContext, Injectable } from '#nestjs/common';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
canActivate(context: ExecutionContext): boolean {
try {
// Override: handle authorization
// return true or false
// Should JwtStrategy.validate(something) be called here?
} catch (e) {
console.log(e);
return false;
}
}
}
Then I tried to find the original code of AuthGuard('jwt') in order to understand its logic, but I was not able to. Any help would be appreciated, thanks!
Okay, so this is gonna be a very fun deep dive into this. Buckle up.
Middleware as express methods do still exist in NestJS; that said, this is not your normal middleware in the sense of Express middleware. As you'v mentioned AuthGuard()#canActivate() ends up calling the appropriate PassportStrategy. These strategies get registered here specifically on lines 40-41 where passport.use() gets called. This registers the passport strategy class's validate method to be used for passport.verify(). Most of the under the hood logic is very abstract and the context while reading it can be lost, so take your time and understand the concepts of classes, mixins (functions that return classes), and inheritance.
Line 51 of AuthGuard is where the passportFn originally gets created, and in this passportFn passport.authenticate gets called (which calls passport.verify under its hood) (reading through Passport's code is even more confusing, so I'll let you run that when you want).
If you want to add some extra logic to your canActivate() method you can end up calling super.canActivate(context) to call the original canActivate() method that ends up calling passport.authenticate() and thus <Strategy>#validate. That could look something like
#Injectable()
export class CustomAuthGuard extends AuthGuard('jwt') {
async canActivate(context: ExecutionContext): Promise<boolean> {
// custom logic can go here
const parentCanActivate = (await super.canActivate(context)) as boolean; // this is necessary due to possibly returning `boolean | Promise<boolean> | Observable<boolean>
// custom logic goes here too
return parentCanActivate && customCondition;
}
}

How To Mock Repository, Service and Controller In NestJS (Typeorm & Jest)

I'm new at typescript. My Nestjs project app is something like this. I'm trying to use repository pattern, so i separated business logic (service) and persistance logic (repository)
UserRepository
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { UserEntity } from './entities/user.entity';
#Injectable()
export class UserRepo {
constructor(#InjectRepository(UserEntity) private readonly repo: Repository<UserEntity>) {}
public find(): Promise<UserEntity[]> {
return this.repo.find();
}
}
UserService
import { Injectable } from '#nestjs/common';
import { UserRepo } from './user.repository';
#Injectable()
export class UserService {
constructor(private readonly userRepo: UserRepo) {}
public async get() {
return this.userRepo.find();
}
}
UserController
import { Controller, Get } from '#nestjs/common';
import { UserService } from './user.service';
#Controller('/users')
export class UserController {
constructor(private readonly userService: UserService) {}
// others method //
#Get()
public async getUsers() {
try {
const payload = this.userService.get();
return this.Ok(payload);
} catch (err) {
return this.InternalServerError(err);
}
}
}
How do i create unit testing for repository, service & controller without actually persist or retrieve data to DB (using mock)?
Mocking in NestJS is pretty easily obtainable using the testing tools Nest exposes is #nestjs/testing. In short, you'll want to create a Custom Provider for the dependency you are looking to mock, and that's all there is. However, it's always better to see an example, so here is a possibility of a mock for the controller:
describe('UserController', () => {
let controller: UserController;
let service: UserService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
controllers: [UserController],
providers: [
{
provide: UserService,
useValue: {
get: jest.fn(() => mockUserEntity) // really it can be anything, but the closer to your actual logic the better
}
}
]
}).compile();
controller = moduleRef.get(UserController);
service = moduleRef.get(UserService);
});
});
And from there you can go on and write your tests. This is pretty much the same set up for all tests using Nest's DI system, the only thing to be aware of is things like #InjectRepository() and #InjectModel() (Mongoose and Sequilize decorators) where you'll need to use getRepositoryToken() or getModelToken() for the injection token. If you're looking for more exmaples take a look at this repository

How to attach a class level filter with dependency injection?

I am trying to catch failures within a specific controller with filters. My filter requires access to another service (to save in db) and I am not sure how to have a class level filter with dependency injection (DI so that the filter has access to the service).
I've currently passing the service from the controller where I use the decorator UseFilters but realized that decorators don't share the same scope.
#UseFilters(new UnprocessableEntityExceptionFilter(myService))
#Catch(UnprocessableEntityException)
export class UnprocessableEntityExceptionFilter implements ExceptionFilter {
constructor(private readonly requestsService: RequestsService) { }
async catch(exception: UnprocessableEntityException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception.getStatus();
const body = exception.message;
response.status(status).json(body);
await this.requestsService.create(request, response, body);
}
}
And I want to use this filter at the class level like this...
#UseFilters(new UnprocessableEntityExceptionFilter())
export class EventsController {
constructor() { }
#Get()
async get() {
...
}
But I clearly cannot create a new instance of UnprocessableEntityExceptionFilter because it requires dependency injection.
I understand the documentation tells us to use this method when filters have dependency injection, but I don't want this filter to be global.
#Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
I ended up using this
#UseFilters(UnprocessableEntityExceptionFilter)
export class EventsController {}

Nest.js: Global AuthGuard but with exceptions

I would like to register my AuthenticationGuard, which checks for Authentication, globally on my application, so that by default all routes require authentication.
const authGuard = app
.select(AuthModule)
.get(AuthGuard);
app.useGlobalGuards(authGuard);
What is the best/nest.js way to add route exceptions, so that anonymous routes can also be implemented?
You can actually set metadata for the global AuthGuard so it can determine if it should allow an unauthorized request.
e.g.
Set Global Auth Guard
import { Module } from '#nestjs/common';
import { APP_GUARD } from '#nestjs/core';
import { AuthGuard } from './auth.guard';
#Module({
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
})
export class AppModule {}
Use SetMetadata to pass in data to the AuthGuard
import { SetMetadata } from '#nestjs/common';
// Convienience Function
const AllowUnauthorizedRequest = () => SetMetadata('allowUnauthorizedRequest', true);
#Controller()
export class AppController {
#Get('my-unauthorized-path')
#AllowUnauthorizedRequest()
myHandler () {
return { unauthorized: true };
}
}
Use data passed in from SetMetadata to determine if unauthorized request is allowed.
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { validateRequest } from './validateRequest' // your custom implementation
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
const allowUnauthorizedRequest = this.reflector.get<boolean>('allowUnauthorizedRequest', context.getHandler());
return allowUnauthorizedRequest || validateRequest(request);
}
}
There is no way to configure such behavior in a succinct way. If useGlobalGuards used, the only way to do this is to customize or extend AuthGuard.
See original issue

Resources