How to check for roles in NestJS Guard

Viewed 241

I have an external service providing a JWT token. In Nestjs i first have JwtGuard class:

@Injectable()
export class JwtGuard extends AuthGuard('JWT_STRATEGY') {
  constructor() {
    super();
  }

  getRequest(context: ExecutionContext) {
    console.log('JwtGuard');
    const ctx = GqlExecutionContext.create(context);
    return ctx.getContext().req;
  }
}

and then a passport strategy:

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'JWT_STRATEGY') {
  constructor(private configService: ConfigService) {
    super({
      secretOrKeyProvider: passportJwtSecret({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: configService.get<string>('ADFS_KEYS_URL'),
      }),
      ignoreExpiration: false,
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      audience: configService.get<string>('ADFS_AUDIENCE'),
      issuer: configService.get<string>('ADFS_ISSUER'),
      algorithms: ['RS256'],
    });
  }

  validate(payload: unknown): unknown {
    console.log('jwt strategy');
    console.log(payload);
    return payload;
  }
}

It seems that JwtGuard is running first, then the strategy. But if i want to do additional guards and checks, say for roles. Where does one do that? Do i need another guard that runs after the passport strategy? I have two roles "User" and "Admin".

1 Answers

First of all, define a global guard (called RolesGuard) in the AppModule as following:

providers: [
  AppService,
  {
    provide: APP_GUARD,
    useClass: JwtAuthGuard,
  },
  {
    provide: APP_GUARD,
    useClass: RolesGuard,
  },
]

Then within RolesGuard we have the following:

export enum FamilyRole {
  Admin = 'Admin',
  User = 'User',
}

...

export class FamilyRolesGuard implements CanActivate {

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const requiredRoles = this.reflector.getAllAndOverride<FamilyRole>(
      ROLES_KEY,
      [context.getHandler(), context.getClass()],
    );

    if (!requiredRoles) {
      return true;
    }

    const { user } = context.switchToHttp().getRequest();
    // do the rest and return either true or false
  }
}

Then create your own decorator and you can decorate your APIs if you need that API to protect your app based on your guard.

import { SetMetadata } from '@nestjs/common';

export const ROLES_KEY = 'FamilyRoles';
export const FamilyRoles = (...roles: FamilyRole[]) =>
  SetMetadata(ROLES_KEY, roles);

Then you can use your decorator in your API like this:

@Post('user')
@FamilyRoles(FamilyRole.Admin)
...

So, in your API, if you won't have FamilyRoles, in the guard you won't have requiredRoles, and the if block will return true.

More info: https://docs.nestjs.com/security/authorization

Related