NestJS — Enterprise Node.js

Guards: Authorization

17 min Lesson 13 of 30

Guards: Authorization

A guard answers one question: should this request be allowed to proceed? It returns true to allow the request through to the handler, or false (or throws) to block it. Guards are the standard place for authentication and authorization logic.

The CanActivate interface

A guard is an injectable class implementing CanActivate, with a single canActivate() method. It receives an ExecutionContext and returns a boolean (or a Promise/Observable of one):

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; @Injectable() export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); return Boolean(request.headers['authorization']); } }

If canActivate() returns false, NestJS automatically responds with 403 Forbidden and the handler never runs.

The ExecutionContext

ExecutionContext describes the current request in a transport-agnostic way. switchToHttp() gives you the HTTP request/response, but the same guard could work for WebSockets or microservices via switchToWs() / switchToRpc(). It also exposes the target class and handler — essential for reading metadata.

Applying guards

Bind a guard with @UseGuards() at the method or controller level, or globally in main.ts:

import { UseGuards } from '@nestjs/common'; @Controller('admin') @UseGuards(AuthGuard) // protects every route in the controller export class AdminController {} // or globally: app.useGlobalGuards(new AuthGuard());

Role-based authorization with metadata

Guards become powerful when combined with custom metadata. You attach required roles to a handler with @SetMetadata() (or a custom decorator), then read them inside the guard using the Reflector:

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const required = this.reflector.get('roles', context.getHandler()); if (!required) return true; // no roles required -> allow const { user } = context.switchToHttp().getRequest(); return required.some((role: string) => user?.roles?.includes(role)); } } // on a route: @SetMetadata('roles', ['admin']) @Get('reports') getReports() {}
The Reflector reads metadata. It is how a single, reusable guard adapts its behaviour per route — the decorator declares the requirement, the guard enforces it.

Guards vs middleware

Why a guard, not middleware, for auth? Guards run after middleware and have the full ExecutionContext — they know exactly which handler is targeted and can read its metadata (like required roles). Middleware runs too early to know any of that.
Guards decide access, not data transformation. A guard should return allow/deny. Shaping the response or enriching data is the job of interceptors, covered next.

Summary

Guards implement CanActivate and return true/false to allow or block a request, responding 403 on denial. They receive an ExecutionContext (transport-agnostic), are applied with @UseGuards(), and become role-aware by reading route metadata via the Reflector. Guards run after middleware and are the right place for authorization. Next: interceptors, which wrap the handler itself.