Guards — Allow or Deny

Decide Whether a Request Continues

Guards — Allow or Deny

A guard returns a boolean (or throws). True means continue; false means 403. They're how you enforce auth and authorization in Nest.

4 min read Level 2/5 #nestjs#guards#security
What you'll learn
  • Implement the CanActivate interface
  • Apply guards with @UseGuards
  • Inject services and access the request inside a guard

Where pipes ask “is this argument valid?”, guards ask “should this request even be running?”. They run before pipes and the handler, get the full ExecutionContext, and either say yes or short-circuit with a 403.

A Minimal Guard

Implement CanActivate. Return true to continue, false to forbid, or throw a specific exception.

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';

@Injectable()
export class ApiKeyGuard implements CanActivate {
  canActivate(ctx: ExecutionContext): boolean {
    const req = ctx.switchToHttp().getRequest();
    return req.headers['x-api-key'] === process.env.API_KEY;
  }
}

Applying a Guard

Attach with @UseGuards. It works at controller scope (every route in the controller) or method scope (a single route).

@Controller('admin')
@UseGuards(ApiKeyGuard)        // applies to every route below
export class AdminController {
  @Get('stats')
  stats() { return this.svc.stats(); }

  @Post('purge')
  @UseGuards(SuperAdminGuard)  // stacks: both must pass
  purge() { return this.svc.purge(); }
}

For app-wide rules, use app.useGlobalGuards(...) in main.ts.

Injecting Services

A guard is just an @Injectable(), so it can inject anything — repositories, config, a JWT verifier.

@Injectable()
export class TenantGuard implements CanActivate {
  constructor(private readonly tenants: TenantsService) {}

  async canActivate(ctx: ExecutionContext): Promise<boolean> {
    const req = ctx.switchToHttp().getRequest();
    const tenant = await this.tenants.findByDomain(req.hostname);
    if (!tenant) return false;
    req.tenant = tenant;                // attach for downstream use
    return true;
  }
}

Notice that canActivate can be async — return a Promise<boolean> or an Observable<boolean> and Nest waits.

Throwing for Better Errors

Returning false produces a generic ForbiddenException. Throw your own exception when you want a specific status code or message.

import { UnauthorizedException } from '@nestjs/common';

canActivate(ctx: ExecutionContext): boolean {
  const req = ctx.switchToHttp().getRequest();
  if (!req.headers.authorization) {
    throw new UnauthorizedException('missing bearer token');
  }
  return verify(req.headers.authorization);
}

Where Guards Run

Pipeline order, top to bottom: middleware → guards → interceptors (pre) → pipes → handler. Guards see the request after middleware has had a chance to mutate it (so cookies are parsed, the user is attached) and before validation runs. That’s the right point to gate access.

The next lesson focuses on the most common guard of all: auth.

Auth Guards →