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.
A guard is a class annotated with the @Injectable() decorator, which implements the CanActivate interface. Guards have a single responsibility. They determine whether a given request will be handled by the route handler or not, depending on certain conditions (like permissions, roles, ACLs, etc.) present at run-time.
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.
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;
}
}
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.
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();
}
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.
In my code I did the following:
@UseGuards(AuthGuard(["jwt", "api-key"]))
@Get()
getOrders() {
return this.orderService.getAllOrders();
}
In Postman, the endpoint can have the api-key and the JWT.
That implies there is an OR function between the 2 Guards.
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')])
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With