NestJS in Production · Structure, Validation and Errors

Authentication and Role Guards

Protect routes with guards: an authentication guard that identifies the user and a roles guard driven by route metadata.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

An AuthGuard that reads a bearer token and attaches the user, a custom @Roles("admin") decorator, and a RolesGuard that reads that metadata with the Reflector. Public routes are marked with @Public().

The idea

  • A guard (canActivate) decides whether a request may proceed; return false (403) or throw (for example 401).
  • Guards run after middleware and before pipes and handlers, and can read route metadata set by decorators (SetMetadata, Reflector.createDecorator).
  • Register the auth guard globally (secure by default) and opt routes out with a @Public() decorator, so a forgotten decorator doesn't create an open endpoint.

The code

Global auth guard, @Public() and @Roles()
import "reflect-metadata";
import { CanActivate, Controller, ExecutionContext, Get, Injectable, Module, UnauthorizedException } from "@nestjs/common";
import { APP_GUARD, NestFactory, Reflector } from "@nestjs/core";

const Public = Reflector.createDecorator<boolean>();
const Roles = Reflector.createDecorator<string[]>();
const TOKENS: Record<string, { name: string; roles: string[] }> = {
  "token-ana": { name: "ana", roles: ["admin"] },
  "token-ben": { name: "ben", roles: ["user"] },
};

@Injectable()
class AuthGuard implements CanActivate {
  constructor(private reflector: Reflector) {}
  canActivate(ctx: ExecutionContext) {
    if (this.reflector.getAllAndOverride(Public, [ctx.getHandler(), ctx.getClass()])) return true;
    const req = ctx.switchToHttp().getRequest();
    const user = TOKENS[(req.headers.authorization ?? "").replace("Bearer ", "")];
    if (!user) throw new UnauthorizedException("missing or invalid token");
    req.user = user;
    return true;
  }
}

@Injectable()
class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}
  canActivate(ctx: ExecutionContext) {
    const required = this.reflector.getAllAndOverride(Roles, [ctx.getHandler(), ctx.getClass()]);
    if (!required) return true;
    return required.some((r) => ctx.switchToHttp().getRequest().user?.roles.includes(r));
  }
}

@Controller()
class AppController {
  @Public() @Get("health") health() { return { ok: true }; }
  @Get("me") me() { return { ok: "any signed-in user" }; }
  @Roles(["admin"]) @Get("admin/report") report() { return { revenue: 1200 }; }
}

@Module({ controllers: [AppController], providers: [{ provide: APP_GUARD, useClass: AuthGuard }, { provide: APP_GUARD, useClass: RolesGuard }] })
class AppModule {}

async function main() {
  const app = await NestFactory.create(AppModule, { logger: false });
  await app.listen(0, "127.0.0.1");
  const base = `http://127.0.0.1:${app.getHttpServer().address().port}`;
  const calls: [string, string?][] = [["/health"], ["/me"], ["/me", "token-ben"], ["/admin/report", "token-ben"], ["/admin/report", "token-ana"]];
  for (const [path, token] of calls) {
    const r = await fetch(base + path, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
    console.log(`${path} as ${token ?? "anonymous"} -> ${r.status}`);
  }
  await app.close();
}
main();

Output

/health as anonymous -> 200
/me as anonymous -> 401
/me as token-ben -> 200
/admin/report as token-ben -> 403
/admin/report as token-ana -> 200

Run this on your computer — see “Get Started” for setup.

Common mistakes

  • Adding @UseGuards(AuthGuard) per controller and forgetting one: make authentication global and opt out explicitly.
  • Checking roles but not ownership: a user role may still only access its own resources.
  • Using guards for input validation (that's pipes) or response shaping (interceptors).

Key points

  • Guards decide access before the handler runs.
  • Global auth guard plus @Public() makes endpoints secure by default.
  • Roles come from route metadata read with the Reflector.

Further reading: Guards · Authorization

Practice questions

Why register the authentication guard globally?

Answer

Every route is protected by default; public routes must opt out explicitly, so a forgotten decorator can't create an unauthenticated endpoint.

When do guards run relative to pipes and interceptors?

Answer

After middleware, before interceptors' pre-handler logic and pipes; they decide whether the request reaches the handler at all.

How does a roles guard know which roles a route requires?

Answer

From metadata attached by a decorator (Reflector.createDecorator or SetMetadata), read in the guard with the Reflector.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?