How to ignore some routes from #UseGuards() in a controller? - nestjs

I have a controller like this:
#ApiBearerAuth()
#UseGuards(AuthGuard('jwt'))
#ApiTags('books')
#Controller('books')
export class BooksController {
#Post()
async create(#Body() createBookVm: CreateBookVm) {
//........
}
#Get()
async all() {
//........
}
}
When I access all() rout in above controller without accessToken I get the foloowing error:
{"statusCode":401,"error":"Unauthorized"}
It is a correct behavior but I want ignore all() action from general #UseGuards of the controller. I want access it as a public rout without authorization.

The easiest way is to change Guards to routes:
#ApiBearerAuth()
#ApiTags('books')
#Controller('books')
export class BooksController {
#Post()
#UseGuards(AuthGuard('jwt'))
async create(#Body() createBookVm: CreateBookVm) {
//........
}
#Get()
async all() {
//........
}
}

To provide another answer, albeit one that requires more code, is you could create a custom decorator that assigns metadata to the class and/or the class method. This metadata, in theory, would be for telling the guard to skip the auth check on this entire class, or on this route (depending on how you set the metadata up), and return true so that the request can still flow.
I've got a decorator like this set up here that sets up metadata if you'd like to take a look at how it works.
With this kind of approach, you could bind the guard globally, and then add the #AuthSkip() (or whatever you call it) decorator to the routes or classes you don't want to authorize.
Now you'll need to extend the AuthGuard('jwt') and update the canActivate() method to check for this metadata in the current context. This means that you'll need to add the Reflector as a dependency to the guard class and use it to get the metadata from both the class and the current route (if you went so far as to make it work for ignoring classes and not just routes), and if the metadata exists, then the route was to be skipped, return true from the guard. I make that kind of check here if you'd like to see an example of that in action.

Assuming you have used the app.useGlobalGuards() method inside main.ts file, add the following code inside the auth.guard.ts file:
import { ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { AuthGuard as PassportAuthGaurd } from '#nestjs/passport';
#Injectable()
export class AuthGuard extends PassportAuthGaurd('jwt') {
constructor(private readonly reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.get<boolean>(
'isPublic',
context.getHandler()
);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
}
I had used pssport jwt method here, but you can alter it according to you, just remember to keep constructor and the logic of canActivate same.
Now in your main.ts modify global guard so we can use Reflectors in it:
const reflector = app.get(Reflector);
app.useGlobalGuards(new AuthGuard(reflector));
Now in order to make routes public we would use a custom decorator, for that create a file named public.decorator.ts and add the following code:
import { SetMetadata } from '#nestjs/common';
export const Public = () => SetMetadata('isPublic', true);
Here we have added a custom metadata value which is same value that we used inside our auth.guard.ts file. Now just add this #Public() decorator on the route that you want to make public:
#Get()
#Public()
async all() {
//........
}
Now your all function won't check for the token authentication.
I found this blog which does the same thing, you can check it out.

Related

Access a repo in a guard in nestjs

Before upgrading to typeorm 0.3 I could use getConnection().getRepository<User>(User) in my guard to get a repo for a type and operate on it.
With 0.3 however that is deprecated (see also https://newreleases.io/project/github/typeorm/typeorm/release/0.3.0) and now I cannot get access to the db in my guard anymore. I tried to use
#InjectRepository(User)
private userRepo: Repository<User>,
in the guard's constructor and then tried to make the guard a provider from a module that I exported but also that didnt work.
So I wonder how to get access to a repo or connection there. Otherwise I would probably need to pass my connection details to the Guard and create a new connecion ther which seems aweful.
You can try with mixin https://wanago.io/2021/12/13/api-nestjs-mixin-pattern/.
Check the section Passing additional arguments.
I have done it with DataSource you can use Repository as well.
//scope.guard.ts
import { CanActivate, ExecutionContext, NotFoundException, Inject, Type, mixin } from "#nestjs/common";
import { DataSource, getRepository, ObjectType } from "typeorm";
import { Request } from "express";
const ScopeGuard = (entityClass): Type<CanActivate> => {
class ScopeGuardMixin {
constructor(#Inject(DataSource) private readonly dataSource: DataSource) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
console.log("here 1");
console.log(entityClass);
console.log("here 2");
const request = context.switchToHttp().getRequest<Request>();
console.log(await this.dataSource.getRepository(entityClass).findOneBy({ id: Number(request.params.id) }));
return true;
}
}
return mixin(ScopeGuardMixin);
};
export default ScopeGuard;
Controller code
import ScopeGuard from "app/modules/guards/scope.guard";
#UseGuards(ScopeGuard(User))

NestJS - Combine multiple Guards and activate if one returns true

Is it possible to use multiple auth guards on a route (in my case basic and ldap auth).
The route should be authenticated when one guard was successful.
Short answer: No, if you add more than one guard to a route, they all need to pass for the route to be able to activate.
Long answer: What you are trying to accomplish is possible however by making your LDAP guard extend the basic one. If the LDAP specific logic succeeds, return true, otherwise return the result of the call to super.canActivate(). Then, in your controller, add either the basic or LDAP guard to your routes, but not both.
basic.guard.ts
export BasicGuard implements CanActivate {
constructor(
protected readonly reflector: Reflector
) {}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
if () {
// Do some logic and return true if access is granted
return true;
}
return false;
}
}
ldap.guard.ts
export LdapGuard extends BasicGuard implements CanActivate {
constructor(
protected readonly reflector: Reflector
) {
super(reflector);
}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
if () {
// Do some logic and return true if access is granted
return true;
}
// Basically if this guard is false then try the super.canActivate. If its true then it would have returned already
return await super.canActivate(context);
}
}
For more information see this GitHub issue on the official NestJS repository.
According to AuthGuard it just works out of the box
AuthGuard definition
If you look at AuthGuard then you see the following definition:
(File is node_modules/#nestjs/passport/dist/auth.guard.d.ts)
export declare const AuthGuard: (type?: string | string[]) => Type<IAuthGuard>;
That means that AuthGuard can receive an array of strings.
Code
In my code I did the following:
#UseGuards(AuthGuard(["jwt", "api-key"]))
#Get()
getOrders() {
return this.orderService.getAllOrders();
}
Postman test
In Postman, the endpoint can have the api-key and the JWT.
Tested with JWT in Postman Authorization: It works
Tested with API-Key in Postman Authorization: It works
That implies there is an OR function between the 2 Guards.
You can create an abstract guard, and pass instances or references there, and return true from this guard if any of the passed guards returned true.
Let's imagine you have 2 guards: BasicGuard and LdapGuard. And you have a controller UserController with route #Get(), which should be protected by these guards.
So, we can create an abstract guard MultipleAuthorizeGuard with next code:
#Injectable()
export class MultipleAuthorizeGuard implements CanActivate {
constructor(private readonly reflector: Reflector, private readonly moduleRef: ModuleRef) {}
public canActivate(context: ExecutionContext): Observable<boolean> {
const allowedGuards = this.reflector.get<Type<CanActivate>[]>('multipleGuardsReferences', context.getHandler()) || [];
const guards = allowedGuards.map((guardReference) => this.moduleRef.get<CanActivate>(guardReference));
if (guards.length === 0) {
return of(true);
}
if (guards.length === 1) {
return guards[0].canActivate(context) as Observable<boolean>;
}
const checks$: Observable<boolean>[] = guards.map((guard) =>
(guard.canActivate(context) as Observable<boolean>).pipe(
catchError((err) => {
if (err instanceof UnauthorizedException) {
return of(false);
}
throw err;
}),
),
);
return forkJoin(checks$).pipe(map((results: boolean[]) => any(identity, results)));
}
}
As you can see, this guard doesn't contain any references to a particular guard, but only accept the list of references. In my example, all guards return Observable, so I use forkJoin to run multiple requests. But of course, it can be adopted to Promises as well.
To avoid initiating MultipleAuthorizeGuard in the controller, and pass necessary dependencies manually, I'm left this task to Nest.js and pass references via custom decorator MultipleGuardsReferences
export const MultipleGuardsReferences = (...guards: Type<CanActivate>[]) =>
SetMetadata('multipleGuardsReferences', guards);
So, in controller we can have next code:
#Get()
#MultipleGuardsReferences(BasicGuard, LdapGuard)
#UseGuards(MultipleAuthorizeGuard)
public getUser(): Observable<User> {
return this.userService.getUser();
}
You can use combo guard that injects all guards what you need and combines their logic.
There is closed github issue:
https://github.com/nestjs/nest/issues/873
There is also a npm package that address this scenario: https://www.npmjs.com/package/#nest-lab/or-guard.
Then you call a unique guard that references all the necessary guards as parameters:
guards([useGuard('basic') ,useGuard('ldap')])
Inspired from https://stackoverflow.com/a/69966319/16730890
This uses Promise instead of Observable.
import {
CanActivate,
ExecutionContext,
Injectable,
SetMetadata,
Type,
} from '#nestjs/common';
import { ModuleRef, Reflector } from '#nestjs/core';
#Injectable()
export class MultipleAuthorizeGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
private readonly moduleRef: ModuleRef,
) {}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const allowedGuards =
this.reflector.get<Type<CanActivate>[]>(
'multipleGuardsReferences',
context.getHandler(),
) || [];
const guards = allowedGuards.map((guardReference) =>
this.moduleRef.get<CanActivate>(guardReference),
);
if (guards.length === 0) {
return Promise.resolve(true);
}
if (guards.length === 1) {
return guards[0].canActivate(context) as Promise<boolean>;
}
return Promise.any(
guards.map((guard) => {
return guard.canActivate(context) as Promise<boolean>;
}),
);
}
}
export const MultipleGuardsReferences = (...guards: Type<CanActivate>[]) =>
SetMetadata('multipleGuardsReferences', guards);
#Get()
#MultipleGuardsReferences(BasicGuard, LdapGuard)
#UseGuards(MultipleAuthorizeGuard)
public getUser(): Promise<User> {
return this.userService.getUser();
}

Inject services on demand on Guard

I have a guard that checks the ownership of a resource. If the user is the owner (created that resource), then he can access (update, read, delete).
Each resource is handled by its own service (comments is handled by the CommentsModule which has the CommentsService and so on). Each service that handles a resource with ownership implements a function called hasOwnership and the guard will call said function.
If possible, I would like my guard to inject the correct service depending on the controller that is calling it. So, if CommentsController is calling the guard, then it should inject and use CommentsService.hasOwnership.
I have tried using dynamic modules to inject the correct module/service on the imports of ACModule which hosts the guard, but that seems to be a no go as I can't properly handle the circular dependencies.
Injecting every service into the guard and selecting the correct will be very troublesome to mantain, due to the circular dependencies.
Is there a better way? This would be the desirable behavior.
#Injectable()
export default class ACGuard implements CanActivate {
constructor(
#Inject('SERVICE_KEY') private correctService
) {}
canActivate(context: ExecutionContext) {
return await correctService.hasOwnership();
}
}
Because you need a different service at run time (instead of at compile time) you are going to need to take a factory approach. It is a little troublesome to maintain as new resources are added but that's the trade-off you have to make.
First thing I would do is create a factory class to determine which service is the correct service to use based on the ExecutionContext:
export interface IService {
hasOwnership() : Promise<boolean>;
}
#Injectable()
export class ServiceFactory {
//Make sure every service returned from this method implements the "IService" interface
public getCorrectService(context: ExecutionContext) : IService {
if(context...) {
return new CommentsService();
} else if(context...) {
return new SomeOtherService();
}
}
}
Now you can inject that factory into your guard to get the correct service:
#Injectable()
export default class ACGuard implements CanActivate {
constructor(private serviceFactory: ServiceFactory) {}
canActivate(context: ExecutionContext) {
//Here's where the magic of this happens...
const correctService: Iservice = this.serviceFactory.getCorrectService(context);
return await correctService.hasOwnership();
}
}

Is it possible to override global scoped guard with controller/method scoped one

I'm writing webAPI using NestJS framework. I was not able to override global scoped guard with the one placed on method or controller level. All of my endpoints will use JWT verification guard except one used for logging into the system. Is it possible to create one guard on root level and only override this global guard with #UseGuard() decorator on single method level?
I tried to use guard before listen function call and also use APP_GUARDprovider, but in both cases I'm not able to override this behavior.
Code example:
https://codesandbox.io/embed/nest-yymkf
Just to add my 2 cents.
Instead of defining 2 guards (reject and accept) as the OP have done, I have defined a custom decorator:
import { SetMetadata } from '#nestjs/common'
export const NoAuth = () => SetMetadata('no-auth', true)
The reject guard (AuthGuard) uses Reflector to be able to access the decorator's metadata and decides to activate or not based on it.
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common'
import { Reflector } from '#nestjs/core'
import { Observable } from 'rxjs'
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const noAuth = this.reflector.get<boolean>('no-auth', context.getHandler())
if(noAuth) return true
// else your logic here
}
}
I then bind the reject guard globally in some module:
#Module({
providers: [{
provide: APP_GUARD,
useClass: AuthGuard
}]
})
and proceed to use the decorator where needed:
#NoAuth()
#Get() // anyone can access this
getHello(): string {
return 'Hello Stranger!'
}
#Get('secret') // protected by the global guard
getSecret(): string {
return 'ssshhh!'
}
After a posting the question I figured out the solution for my problem. I should add some custom meta-data into my controller and put a logic inside the guard to read that meta-data.
I have updated the code sample.

Access Nest "injector" in a custom interceptor

I need to access a service (provided by Nest TypeOrmModule) inside the intercept function (important note: not as constructor parameter!!!) because it depends of the passed options (entity in this case).
The service injection token is provided by the getRepositoryToken function.
export class PaginationInterceptor {
constructor(private readonly entity: Function) {}
intercept(context: ExecutionContext, call$: Observable<any>): Observable<any> {
// Here I want to inject the TypeORM repository.
// I know how to get the injection token, but not HOW to
// get the "injector" itself.
const repository = getRepositoryToken(this.entity);
// stuff...
return call$;
}
}
Is any concept of "service container" in Nest? This github issue didn't help me.
Example usage (controller action):
#Get()
#UseInterceptors(new PaginationInterceptor(Customer))
async getAll() {
// stuff...
}
Regarding dependency injection (if you really want/need it), I guess using a mixin class can do the trick. See the v4 documentation (Advanced > Mixin classes).
import { NestInterceptor, ExecutionContext, mixin, Inject } from '#nestjs/common';
import { getRepositoryToken } from '#nestjs/typeorm';
import { Observable } from 'rxjs';
import { Repository } from 'typeorm';
export function mixinPaginationInterceptor<T extends new (...args: any[]) => any>(entityClass: T) {
// declare the class here as we can't give it "as-is" to `mixin` because of the decorator in its constructor
class PaginationInterceptor implements NestInterceptor {
constructor(#Inject(getRepositoryToken(entityClass)) private readonly repository: Repository<T>) {}
intercept(context: ExecutionContext, $call: Observable<any>) {
// do your things with `this.repository`
return $call;
}
}
return mixin(PaginationInterceptor);
}
Disclaimer: this is valid TypeScript code but I didn't had the chance to test it in a real project, so it might need a bit of rework. The idea is to use it like this:
#UseInterceptors(mixinPaginationInterceptor(YourEntityClass))
Let me know if you have any question about the code. But I think the doc about mixin is pretty good!
OR You can also use getRepository from typeorm (passing the entity class). This is not DI, thus, it will oblige you to spyOn the getRepository function in order to do proper testing.
Regarding the container, I'm almost sure that the only way to access it is using the Execution Context, as pointed by Kim.

Resources