Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Guards

A guard is a class that decides whether a controller method may be called for this request. It returns true — the request goes on, false — the client gets a 403 response. Guards are used for access checks: is the user logged in, do they have the right role.

A simple guard

Let creating posts require a secret key in the x-api-key header. Real password login comes in chapter five; a key is enough for now.

nest g guard common/guards/api-key --flat
// src/common/guards/api-key.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Request } from 'express';
 
@Injectable()
export class ApiKeyGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest<Request>();
    return request.headers['x-api-key'] === 'secret-key';
  }
}

canActivate() receives an ExecutionContext — information about the current request. Through it the guard gets the Express request object and reads the header.

Applying it to a method

// src/posts/posts.controller.ts
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { ApiKeyGuard } from '../common/guards/api-key.guard';
import { CreatePostDto } from './dto/create-post.dto';
import { PostsService } from './posts.service';
 
@Controller('posts')
export class PostsController {
  constructor(private readonly postsService: PostsService) {}
 
  @Post()
  @UseGuards(ApiKeyGuard)
  create(@Body() dto: CreatePostDto) {
    return this.postsService.create(dto);
  }
}

Send a POST request without the header — you get 403 Forbidden resource. With the header the post is created:

curl -X POST http://localhost:3000/posts \
  -H "Content-Type: application/json" \
  -H "x-api-key: secret-key" \
  -d '{"title":"Guards","content":"Access check"}'

Where to put a guard

WhereHowApplies to
Method@UseGuards(ApiKeyGuard) above the methodOne route
Controller@UseGuards(ApiKeyGuard) above the classAll routes of the controller
ApplicationAn APP_GUARD provider in a moduleAll routes

A global guard is registered in any module:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { ApiKeyGuard } from './common/guards/api-key.guard';
 
@Module({
  providers: [{ provide: APP_GUARD, useClass: ApiKeyGuard }],
})
export class AppModule {}

Registered this way, the guard can receive services through its constructor — you will need that for token login.

Your own error code

Instead of returning false, a guard can throw an exception. The client then gets its code and text:

// src/common/guards/api-key.guard.ts
import { UnauthorizedException } from '@nestjs/common';
 
if (request.headers['x-api-key'] !== 'secret-key') {
  throw new UnauthorizedException('Invalid API key');
}
return true;

Code 401 means "you did not identify yourself", 403 means "you did, but you have no access".

Common mistake

A guard runs after all middleware but before pipes. So the guard has no validated DTO yet: the request body there is raw data. Do not rely on body fields in a guard; check headers and the user there, and validate data in the DTO.

Thanks for reading!

Donate to the site — we work hard for you.