On this page

Guards and authorization in NestJS

14 min read TextCh. 2 — Services and Injection

What is a guard?

A guard is a class that implements CanActivate and determines whether a request should proceed to the route handler. Guards return true to allow the request or false (or throw an exception) to reject it.

Guards are the NestJS mechanism for authentication and authorization:

  • Authentication — Verify the identity of the requester (is the token valid? does the user exist?)
  • Authorization — Verify the requester has permission (is the user an admin? does she own this resource?)

Guards run after middleware but before pipes and interceptors. This placement is intentional: authentication must be verified before any expensive processing happens.

The CanActivate interface

Every guard implements one method: canActivate(context: ExecutionContext). The ExecutionContext provides access to the current request in the appropriate format:

// HTTP context
const request = context.switchToHttp().getRequest<Request>();
const response = context.switchToHttp().getResponse<Response>();

// WebSocket context
const client = context.switchToWs().getClient();
const data = context.switchToWs().getData();

// RPC context
const rpcContext = context.switchToRpc().getContext();

The context.getHandler() and context.getClass() methods return the route handler function and the controller class, respectively. These are used with Reflector to read metadata attached by decorators.

Applying guards

Guards can be applied at three levels:

Method level

@Get('admin-report')
@UseGuards(AuthGuard, RolesGuard)
getAdminReport() { ... }

Controller level

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

Global level

// In main.ts — for dependency-injection-aware guards
const app = await NestFactory.create(AppModule);
const jwtService = app.get(JwtService);
app.useGlobalGuards(new AuthGuard(jwtService));

// Better: register in the module
// In app.module.ts
@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: AuthGuard,
    },
  ],
})
export class AppModule {}

Registering with APP_GUARD is preferred for global guards because it participates in the DI system — the guard can inject services normally.

The Public decorator pattern

When AuthGuard is applied globally, every route requires a token. To mark routes as public (no token needed), create a custom decorator:

// public.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

Then check it in the guard:

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    private readonly jwtService: JwtService,
    private readonly reflector: Reflector,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (isPublic) return true;

    // ... token verification logic
    return true;
  }
}

Usage:

@Public()
@Get()
findAll() { ... } // No authentication required

@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) { ... } // Authentication required

Metadata with Reflector

The Reflector service reads metadata set by decorators. getAllAndOverride is the recommended method — it checks the handler first, then the class, and returns the first match:

const roles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
  context.getHandler(), // Method-level decorator takes precedence
  context.getClass(),   // Falls back to controller-level decorator
]);

getAllAndMerge is an alternative that combines metadata from all levels into a single array, useful when you want to accumulate roles from multiple decorators.

Accessing the current user

After AuthGuard attaches the decoded JWT payload to the request, route handlers need to access it. Create a custom parameter decorator:

// current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { Request } from 'express';

export const CurrentUser = createParamDecorator(
  (data: keyof JwtPayload | undefined, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest<Request>();
    const user = request['user'] as JwtPayload;
    return data ? user?.[data] : user;
  },
);

interface JwtPayload {
  sub: number;
  email: string;
  role: string;
}

Usage in a controller:

@Get('profile')
getProfile(@CurrentUser() user: JwtPayload) {
  return user;
}

@Get('me/id')
getMyId(@CurrentUser('sub') userId: number) {
  return { userId };
}

Resource-level authorization

Checking whether a user can act on a specific resource (e.g., "can this user edit this book?") is more granular than role checks. Implement it as a guard that queries the database:

@Injectable()
export class BookOwnerGuard implements CanActivate {
  constructor(private readonly booksService: BooksService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest<Request>();
    const userId = request['user']?.sub as number;
    const bookId = +request.params['id'];

    const book = await this.booksService.findOne(bookId);

    if (book.createdById !== userId) {
      throw new ForbiddenException('You do not own this book');
    }

    return true;
  }
}

Combining authentication and authorization

A typical setup uses three guards applied in order:

  1. AuthGuard — Verifies JWT and attaches user to request
  2. RolesGuard — Checks user role against @Roles() metadata
  3. BookOwnerGuard — Checks ownership for specific resources

Apply them together:

@Put(':id')
@Roles('admin', 'author')
@UseGuards(AuthGuard, RolesGuard, BookOwnerGuard)
update(
  @Param('id', ParseIntPipe) id: number,
  @Body() updateBookDto: UpdateBookDto,
  @CurrentUser() user: JwtPayload,
) {
  return this.booksService.update(id, updateBookDto, user);
}

Guards execute left to right: AuthGuard must succeed before RolesGuard reads the user, which must succeed before BookOwnerGuard queries the database.

Guard execution order
Guards run after middleware but before interceptors and pipes. When multiple guards are applied (via @UseGuards), they execute in the order they are listed — left to right. This means AuthGuard should always come before RolesGuard, since RolesGuard reads the user attached by AuthGuard.
Global guards and public routes
When you apply AuthGuard globally, all routes require authentication by default. Create a @Public() decorator that sets metadata, then check for it in AuthGuard — if the route is marked public, skip verification and return true.
import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private readonly jwtService: JwtService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest<Request>();
    const token = this.extractToken(request);

    if (!token) {
      throw new UnauthorizedException('No token provided');
    }

    try {
      const payload = await this.jwtService.verifyAsync<JwtPayload>(token);
      request['user'] = payload;
      return true;
    } catch {
      throw new UnauthorizedException('Invalid or expired token');
    }
  }

  private extractToken(request: Request): string | undefined {
    const [type, token] = request.headers.authorization?.split(' ') ?? [];
    return type === 'Bearer' ? token : undefined;
  }
}

interface JwtPayload {
  sub: number;
  email: string;
  role: string;
}