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.
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
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.