I have implemented two different JWT based strategies in my application:
- The first strategy involves single sign-on for organization members, where an external provider generates a JWT.
- The second strategy is for email/password authenticated external users, where the application itself creates a JWT.
When it comes to route access, only one of these strategies needs to succeed. However, if multiple guards are declared, all guards must succeed which presents a problem.
For instance, the following code snippet requires both guards to succeed even though realistically only one guard will fulfill the requirements:
@UseGuards(AuthGuard('local-jwt'))
@UseGuards(AuthGuard('azure-ad'))
someRoute(
@CurrentUser currentUser: User,
) {
//...
}
In a solution I found on this GitHub issue, a custom ComposeGuard was created to handle this logic:
@Injectable()
export class ComposeGuard implements CanActivate {
constructor(private allowGuard: AllowGuard, private authGuard: AuthGuard, private roleGuard: RoleGuard) {
}
async canActivate(context: ExecutionContext): Promise<boolean> {
return await this.allowGuard.canActivate(context) || (await this.authGuard.canActivate(context) && await this.roleGuard.canActivate(context));
}
}
While this approach allows for the required custom logic, there is uncertainty regarding how to import guards as dependencies due to guards not being regular classes that can be injected. Additionally, a strategy is a class but does not contain a `canActivate` method.
Another option explored was to make one strategy inherit from the other. However, this solution seems like a messy semantic choice as the strategies run parallel and do not depend on each other.