WardenAuthAgent Security
PricingDocsCompareBlogLearnChangelog
Sign inGet started free
All posts
Tutorial
July 28, 2025
13 min read

Adding Fine-Grained Authorization to a NestJS API

A step-by-step guide to wiring WardenAuth into a NestJS application: registering the module, building a permission guard, using a @RequirePermission decorator, and checking access inside services — with dependency injection throughout.


This guide adds fine-grained, externalized authorization to a NestJS API using WardenAuth’s NestJS module. By the end you will have a reusable @RequirePermission() decorator, a guard that enforces it, and service-level checks — all wired through Nest’s dependency injection.

This assumes NestJS 10+. WardenAuth handles authorization only; keep using your existing auth (JWT/Passport) to establish request.user.

Step 1: Install

bash
npm install @ecarrizo2/wardenauthz-nestjs

Step 2: Register the Module

typescript
// app.module.ts
import { Module } from '@nestjs/common'
import { WardenAuthModule } from '@ecarrizo2/wardenauthz-nestjs'

@Module({
  imports: [
    WardenAuthModule.forRoot({
      apiKey: process.env.RBAC_API_KEY!,
      baseUrl: process.env.RBAC_API_URL!,
    }),
  ],
})
export class AppModule {}

Step 3: A Permission Decorator

Define metadata that says which permission an endpoint requires:

typescript
// require-permission.decorator.ts
import { SetMetadata } from '@nestjs/common'

export interface PermissionRule { resource: string; action: string }
export const PERMISSION_KEY = 'required_permission'
export const RequirePermission = (resource: string, action: string) =>
  SetMetadata(PERMISSION_KEY, { resource, action } as PermissionRule)

Step 4: The Guard

The guard reads the metadata, resolves the scope and subject, and asks WardenAuth for a decision:

typescript
// permission.guard.ts
import { CanActivate, ExecutionContext, Injectable, ForbiddenException } from '@nestjs/common'
import { Reflector } from '@nestjs/core'
import { WardenAuthService } from '@ecarrizo2/wardenauthz-nestjs'
import { PERMISSION_KEY, PermissionRule } from './require-permission.decorator'

@Injectable()
export class PermissionGuard implements CanActivate {
  constructor(
    private readonly reflector: Reflector,
    private readonly accessControl: WardenAuthService,
  ) {}

  async canActivate(ctx: ExecutionContext): Promise<boolean> {
    const rule = this.reflector.get<PermissionRule>(PERMISSION_KEY, ctx.getHandler())
    if (!rule) return true // no rule = public

    const req = ctx.switchToHttp().getRequest()
    const scopeId = req.headers['x-workspace-id']

    const { allowed } = await this.accessControl.accessCheck.check({
      subjectId: req.user.sub,
      scopeId,
      resource: rule.resource,
      action: rule.action,
    })

    if (!allowed) throw new ForbiddenException()
    return true
  }
}

Step 5: Protect Endpoints

typescript
// invoices.controller.ts
@Controller('invoices')
@UseGuards(PermissionGuard)
export class InvoicesController {
  @Delete(':id')
  @RequirePermission('invoice', 'delete')
  async remove(@Param('id') id: string) {
    return this.invoicesService.remove(id)
  }
}

Step 6: Checks Inside Services

For conditional logic that is not a simple gate, inject the service directly:

typescript
@Injectable()
export class ReportsService {
  constructor(private readonly accessControl: WardenAuthService) {}

  async export(userId: string, scopeId: string) {
    const { allowed } = await this.accessControl.accessCheck.check({
      subjectId: userId, scopeId, resource: 'report', action: 'export',
    })
    if (!allowed) throw new ForbiddenException()
    // ...generate export
  }
}

Summary

You now have declarative, externalized authorization in NestJS — one decorator per endpoint, zero business logic in your handlers, and a single source of truth in WardenAuth. Grab an API key and wire up your first scope.


Back to blogTry WardenAuth free →
© 2026 ecarrizo. All rights reserved.
PricingDocsCompareBlogLearnChangelogStatusGlossaryContactTermsPrivacy