Suggest an editImprove this articleRefine the answer for “What are guards in NestJS and how to implement authentication?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**NestJS guards** implement `CanActivate` and run before route handlers to allow or block requests based on auth. The standard pattern: verify a JWT, attach the decoded payload to the request, return `true`. ```typescript @Injectable() export class JwtAuthGuard implements CanActivate { constructor(private jwtService: JwtService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const req = context.switchToHttp().getRequest(); req.user = await this.jwtService.verifyAsync(extractToken(req)); return true; } } ``` **Key:** Guards run after middleware, before interceptors and pipes. Apply with `@UseGuards()` on a method, controller, or globally via `APP_GUARD`.Shown above the full answer for quick recall.Answer (EN)Image**Guards in NestJS** are classes that implement `CanActivate` and run before route handlers to decide whether a request can proceed, based on authentication status or user permissions. ## Theory ### TL;DR - Think of a guard as a bouncer: it checks the token at the door before the request reaches your controller; no valid token, no entry - `canActivate` returns `true` to allow the request or throws an exception to block it; returning `false` produces a 403 - Guards run after middleware but before interceptors, pipes, and the route handler - Apply with `@UseGuards()` on a method, a controller class, or globally via `APP_GUARD` - Authentication (who are you?) and authorization (what can you do?) are separate concerns; chain two guards to handle both ### Quick example ```typescript import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common'; @Injectable() export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); if (!request.headers.authorization) { throw new UnauthorizedException('No token'); } return true; // request reaches the route handler } } // Protect a single route @Get('profile') @UseGuards(AuthGuard) getProfile() { return 'protected data'; } ``` GET `/profile` without an `Authorization` header returns 401. With the header present, the controller runs. ### How guards fit in the pipeline NestJS processes each request in this order: middleware, guards, interceptors, pipes, route handler, exception filters. Guards sit at position two. They see the request after any middleware parsing and stop it before your business logic runs. If `canActivate` returns `false` or throws, the pipeline stops there. The exception filter catches the thrown error and formats the HTTP response. Your controller code never executes. The `ExecutionContext` object is what makes guards more capable than middleware. It wraps the underlying platform (Express or Fastify) and exposes `context.switchToHttp().getRequest()` for the raw request, plus `context.getHandler()` and `context.getClass()` for reading metadata set by custom decorators like `@Roles('admin')`. ### When to use guards - One route needs auth: `@UseGuards(JwtAuthGuard)` on the method - Whole controller needs auth: `@UseGuards(JwtAuthGuard)` on the class - Entire app protected by default: register via `APP_GUARD` in a module (this supports DI, unlike `app.useGlobalGuards()` in `main.ts`) - Some routes must bypass auth in a globally guarded app: a `@Public()` decorator pattern handles this cleanly - Role-based access on top of identity checks: chain `@UseGuards(JwtAuthGuard, RolesGuard)` ### Guard vs Middleware vs Interceptor | | Guard | Middleware | Interceptor | |---|---|---|---| | Has `ExecutionContext` | Yes | No | Yes | | Can read route metadata | Yes | No | Yes | | Runs before handler | Yes | Yes | Yes (pre-phase) | | Can block request | Yes | Yes | Yes | | Main purpose | Auth, authorization | Parsing, CORS, logging | Response transform, logging | Middleware works at the Express/Fastify layer with no knowledge of NestJS routes or metadata. Guards run inside NestJS and can inspect exactly which controller and method the request is targeting. Use middleware for global HTTP concerns like cookie parsing. Use guards for access decisions. ### Common mistakes **Mistake 1: Not using `async` with `jwtService.verifyAsync`** ```typescript // Wrong - Promise is ignored, guard always returns true canActivate(context: ExecutionContext): boolean { this.jwtService.verifyAsync(token); // never awaited return true; } // Correct async canActivate(context: ExecutionContext): Promise<boolean> { const payload = await this.jwtService.verifyAsync(token); request.user = payload; return true; } ``` **Mistake 2: Skipping `request.user = payload`** The guard validates the token, but the controller still needs the decoded data. Without attaching the payload to `request.user`, every handler that reads `req.user` gets `undefined`. This shows up as a silent production bug and is easy to miss when you mock the guard in tests. **Mistake 3: Forgetting `@Injectable()`** Without `@Injectable()`, NestJS cannot instantiate the guard through its DI container. The app crashes at startup with a confusing DI error. **Mistake 4: Throwing `new Error()` instead of `UnauthorizedException`** `new Error()` bypasses the NestJS exception filter. The response comes back as a 500 with an HTML body instead of a clean 401 JSON object. Use `UnauthorizedException` for 401 and `ForbiddenException` for 403. **Mistake 5: Global guard blocking public routes** A globally registered JWT guard will require a token on `/health`, `/auth/login`, and `/auth/register`. Add the `@Public()` decorator pattern so those routes skip the token check. ### Real-world usage - NestJS + Prisma: guard checks `req.user.id` against the resource owner before allowing writes or deletes - GraphQL resolvers: `@UseGuards()` works on resolvers the same way as on REST controllers; call `context.switchToGraphql()` instead of `switchToHttp()` - Microservices: `JwtAuthGuard` registered globally, only auth endpoints carry `@Public()` - Fastify adapter: guard code stays identical; `switchToHttp()` abstracts the platform difference internally ### Follow-up questions **Q:** What is the full execution order in NestJS? **A:** Middleware runs first. Then guards. Then interceptors (before handler). Then pipes. Then the route handler. Then interceptors again (after handler). Exception filters run last if anything throws. **Q:** How do you chain multiple guards? **A:** `@UseGuards(Guard1, Guard2)`. All guards must return `true`. NestJS stops at the first guard that returns `false` or throws. **Q:** What is the difference between an auth guard and a roles guard? **A:** Auth guard checks identity: is this token valid and does a user exist? Roles guard checks permissions: does this user have the required role? They handle different questions and are typically chained. **Q:** How do you test a guard in isolation? **A:** Create a testing module with the guard as a provider. Mock `ExecutionContext` as a plain object: `{ switchToHttp: () => ({ getRequest: () => mockReq }) }`. Assert `canActivate` returns `true` for valid input and throws for invalid. **Q (Senior):** In a globally registered `JwtAuthGuard`, how do you expose `/auth/login` without a token? **A:** Create a `@Public()` decorator using `SetMetadata('isPublic', true)`. In the guard, use `Reflector.getAllAndOverride` to check for that flag before running token validation. If the route is marked public, return `true` immediately and skip the JWT check entirely. ## Examples ### Basic AuthGuard The simplest guard checks for any `Authorization` header. Good starting point before adding JWT logic. ```typescript import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, } from '@nestjs/common'; @Injectable() export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); if (!request.headers.authorization) { throw new UnauthorizedException('Authorization header is missing'); } return true; // any header value passes; real apps validate the token here } } ``` `@Injectable()` is required. The guard throws `UnauthorizedException` (401) when the header is missing and returns `true` otherwise. Any subsequent guard in the chain runs only if this one passes. ### JWT Auth Guard (production pattern) This guard validates a Bearer token using `@nestjs/jwt`, attaches the decoded payload to `request.user`, and handles expired or malformed tokens with a clean 401. ```typescript // guards/jwt-auth.guard.ts import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { Request } from 'express'; interface AuthRequest extends Request { user?: Record<string, unknown>; } @Injectable() export class JwtAuthGuard implements CanActivate { constructor(private jwtService: JwtService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest<AuthRequest>(); const token = this.extractToken(request); if (!token) throw new UnauthorizedException('No Bearer token'); try { const payload = await this.jwtService.verifyAsync(token, { secret: process.env.JWT_SECRET, }); request.user = payload; // available as req.user in any controller } catch { throw new UnauthorizedException('Invalid or expired token'); } return true; } private extractToken(request: AuthRequest): string | undefined { const [type, token] = request.headers.authorization?.split(' ') ?? []; return type === 'Bearer' ? token : undefined; } } // users.controller.ts @Controller('users') @UseGuards(JwtAuthGuard) export class UsersController { @Get('profile') getProfile(@Req() req: AuthRequest) { return req.user; // { sub: 'userId', email: '...', iat: ... } } } ``` Valid Bearer token returns 200 with the decoded payload. Expired or tampered token returns 401. The `async/await` on `verifyAsync` is not optional; skipping it means the guard always passes regardless of token validity. ### Roles Guard with `@Public()` decorator This covers two production patterns together: role-based access using `Reflector` and marking routes public in a globally guarded app. I have seen the missing `@Public()` on login routes cause real confusion in teams switching from method-level to global guards. ```typescript // decorators/roles.decorator.ts import { SetMetadata } from '@nestjs/common'; export const ROLES_KEY = 'roles'; export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); // decorators/public.decorator.ts import { SetMetadata } from '@nestjs/common'; export const IS_PUBLIC_KEY = 'isPublic'; export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); // guards/roles.guard.ts import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { ROLES_KEY } from '../decorators/roles.decorator'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [ context.getHandler(), // method-level metadata checked first context.getClass(), // then controller-level ]); if (!requiredRoles) return true; // no @Roles() on this route, allow through const { user } = context.switchToHttp().getRequest(); return requiredRoles.some((role) => user.roles?.includes(role)); } } // admin.controller.ts @Controller('admin') @UseGuards(JwtAuthGuard, RolesGuard) // JWT validates identity first, then roles export class AdminController { @Get('users') @Roles('admin') getAllUsers() { return 'admin only'; } } // auth.controller.ts - must be public when JwtAuthGuard is registered globally @Public() @Post('auth/login') login(@Body() loginDto: LoginDto) { return this.authService.login(loginDto); } // app.module.ts - global registration with full DI support @Module({ providers: [ { provide: APP_GUARD, useClass: JwtAuthGuard }, { provide: APP_GUARD, useClass: RolesGuard }, ], }) export class AppModule {} ``` A user with `roles: ['admin']` in the JWT payload passes both guards. A user with `roles: ['viewer']` passes JWT validation but gets a 403 from `RolesGuard`. The `@Public()` decorator on the login route bypasses the JWT check entirely, so unauthenticated users can still get a token.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.