Skip to content

Guards

Guards are the mechanism responsible for Authorization and Access Control. Their sole responsibility is to determine whether a given request should be allowed to proceed to the route handler.

Guards are executed after all middleware but before any interceptor or checking of pipes.

Concept: The Bouncer

A Guard acts as the Security / Bouncer at the restaurant door.

  • They check ID (Authentication).
  • They check the guest list (Authorization/Roles).
  • If the Bouncer says "No", the customer never reaches the Waiter. The request is rejected immediately.

The CanActivate Interface

A Guard is a class annotated with @injectable() that implements the CanActivate interface. It must define a canActivate() method.

This method receives the ExecutionContext and must return:

  • true: The request is allowed.
  • false: The request is denied (Karin automatically throws 403 Forbidden).
  • Promise<boolean>: For asynchronous validation (e.g., database checks).

Example: Basic Authentication Guard

This guard checks for the presence of an Authorization header.

typescript
import {
  CanActivate,
  ExecutionContext,
  injectable,
  UnauthorizedException,
} from "@project-karin/core";

@injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const token = request.headers.get("Authorization");

    if (!token) {
      // It is best practice to throw a specific exception rather than return false
      // to provide a clearer error message (401 vs 403).
      throw new UnauthorizedException("Missing Authorization Token");
    }

    // In a real app, validate the token here
    return true;
  }
}

Execution Context

The ExecutionContext is a powerful utility that provides access to the request details irrespective of the underlying protocol (HTTP, WebSocket, RPC).

typescript
const request = context.switchToHttp().getRequest();
const handler = context.getHandler(); // The method about to be called
const controller = context.getClass(); // The class of the method

This context allows guards to make decisions not just based on the request, but based on where the request is going.


Role-Based Access Control (RBAC)

Guards are most powerful when combined with custom metadata. A common pattern is restricting routes based on user roles.

1. Setting Context (Metadata)

First, the route is tagged with the required roles (see decorators.md).

typescript
@Roles('admin')
@Get('delete')
deleteEverything() { ... }

2. Reading Context (Reflector)

The Guard uses the Reflector helper to read this metadata dynamically.

typescript
import { Reflector } from "@project-karin/core";

@injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    // 1. Retrieve the roles required by the handler
    const requiredRoles = this.reflector.get<string[]>(
      "roles",
      context.getHandler(),
    );

    // 2. If no roles are required, allow access
    if (!requiredRoles) {
      return true;
    }

    // 3. Retrieve the user (attached by a previous AuthGuard)
    const request = context.switchToHttp().getRequest();
    const user = request.user;

    // 4. Validate
    return requiredRoles.some((role) => user?.roles?.includes(role));
  }
}

Binding Guards

Guards can be bound at three different scopes:

1. Method Scope

Applies only to a specific route.

typescript
@Get()
@UseGuards(RolesGuard)
findAll() { ... }

2. Controller Scope

Applies to every route in the controller.

typescript
@Controller('admin')
@UseGuards(RolesGuard)
export class AdminController { ... }

3. Global Scope

Applies to every route in the entire application.

typescript
// main.ts
const app = await KarinFactory.create(adapter, {
  globalGuards: [new AuthGuard()], // Apply to everything
});

WARNING

Global guards registered via the globalGuards array are instantiated outside the DI container context in some edge configurations. If your global guard has complex dependencies, ensure it is registered via a provider pattern or that the dependencies are available.

Released under the MIT License.