Protecting Routes Globally

Secure by Default with @Public

Forgetting @UseGuards(JwtAuthGuard) on one controller leaks data. Safer: make the JWT guard global and mark the few open routes with @Public().

src/auth/public.decorator.ts and src/auth/jwt-auth.guard.ts

import { ExecutionContext, Injectable, SetMetadata } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  constructor(private readonly reflector: Reflector) {
    super();
  }

  canActivate(context: ExecutionContext) {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (isPublic) return true;
    return super.canActivate(context);
  }
}

src/app.module.ts

@Module({
  imports: [AuthModule, UsersModule, ProductsModule, OrdersModule],
  providers: [
    { provide: APP_GUARD, useClass: JwtAuthGuard },   // every route needs a token...
    { provide: APP_GUARD, useClass: RolesGuard },     // ...then roles are checked
  ],
})
export class AppModule {}

// products.controller.ts
// @Public() @Get()      findAll() {}      // browsing the catalogue is open
// @Roles(Role.Admin) @Post() create() {}  // token + admin role