Guards: Authorization and Authentication

Implementing authentication using Passport.js or similar libraries. Creating guards to protect routes based on user roles and permissions.


NestJS Guards: Authentication and Authorization

Introduction to Guards

Guards in NestJS are a critical component for controlling access to your application's endpoints. They act as a gatekeeper, determining whether a request should be handled by the route handler or not. Think of them as middleware but more focused and injectable for specific routes or controllers. They primarily deal with authentication (verifying *who* the user is) and authorization (verifying *what* the user is allowed to do).

Authentication vs. Authorization

Authentication

Authentication verifies the identity of the user making the request. It answers the question: "Who are you?". Common authentication mechanisms include:

  • **Username/Password:** Traditional authentication using stored credentials.
  • **JWT (JSON Web Token):** Token-based authentication where a token is issued after successful login and subsequently used for authorization.
  • **OAuth:** Delegation of authentication to a trusted third-party provider (e.g., Google, Facebook).
  • **API Keys:** Simple authentication mechanism often used for machine-to-machine communication.

In NestJS, authentication guards typically check for the presence of a valid token or credentials in the request and, if valid, attach user information (like the user ID) to the request object.

Authorization

Authorization determines whether an authenticated user has the necessary permissions to access a specific resource or perform a specific action. It answers the question: "Are you allowed to do this?". Authorization mechanisms are often based on:

  • **Roles:** Assigning users to roles (e.g., "admin", "user", "editor") and defining permissions for each role.
  • **Permissions:** Granting specific permissions to users (e.g., "read:articles", "write:comments").
  • **Policies:** Implementing more complex rules based on various factors, such as time of day, user location, or resource ownership.

Authorization guards in NestJS typically check the user's role or permissions against the requirements for the requested endpoint.

Creating and Implementing Guards in NestJS

Let's walk through the process of creating and using guards to protect your routes.

1. Generating a Guard

Use the Nest CLI to generate a guard:

nest g guard auth

This command creates a file `src/auth/auth.guard.ts` (or similar).

2. Implementing the Guard Logic

The core logic of the guard resides within the `canActivate` method. This method should return `true` to allow access or `false` to deny access (or a `Promise` or `Observable` that resolves to `true` or `false`).

Example: Authentication Guard (JWT-based)

 import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';

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

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const token = this.extractTokenFromHeader(request);
    if (!token) {
      throw new UnauthorizedException();
    }
    try {
      const payload = await this.jwtService.verifyAsync(
        token,
        {
          secret: process.env.JWT_SECRET
        }
      );
      // 💡 We're assigning the payload to the request object here
      // so that we can access it in our route handlers
      request['user'] = payload;
    } catch {
      throw new UnauthorizedException();
    }
    return true;
  }

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

**Explanation:**

  • We inject the `JwtService` to verify the JWT.
  • `extractTokenFromHeader` retrieves the JWT from the `Authorization` header (Bearer scheme).
  • If no token is found, or the token is invalid, we throw an `UnauthorizedException`.
  • If the token is valid, we verify it using `jwtService.verifyAsync`.
  • Crucially, we attach the decoded payload (typically containing user information) to the request object (`request['user'] = payload;`). This allows us to access the user information in our controllers.

Example: Authorization Guard (Role-based)

 import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Roles } from './roles.decorator'; // Custom decorator (see below)

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

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<string[]>(Roles, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (!requiredRoles) {
      return true; // Allow access if no roles are specified
    }
    const { user } = context.switchToHttp().getRequest(); // Assuming user is attached by the AuthGuard
    if (!user) {
      return false; //No User Object return false
    }
    return requiredRoles.some((role) => user.roles?.includes(role));
  }
} 

**Explanation:**

  • We use `Reflector` to retrieve metadata associated with the route handler. Specifically, we're looking for a `@Roles` decorator (explained below).
  • If no `@Roles` decorator is present, we allow access (returning `true`). This makes the guard opt-in.
  • We retrieve the `user` object from the request, which is assumed to be attached by the `AuthGuard` (or similar authentication mechanism).
  • We check if the user's roles (assuming `user.roles` is an array of roles) include any of the `requiredRoles` specified by the `@Roles` decorator.

3. Defining Roles Decorator

We use a decorator to specify which roles are required to access a route:

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

export const Roles = (...roles: string[]) => SetMetadata('roles', roles); 

4. Applying the Guards

You can apply guards at the controller level (affecting all routes within the controller) or at the route handler level (affecting only that specific route).

Controller-Level Guard

 import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth/auth.guard';
import { RolesGuard } from './auth/roles.guard';
import { Roles } from './auth/roles.decorator';

@Controller('users')
@UseGuards(AuthGuard, RolesGuard) // Apply both authentication and authorization guards
export class UsersController {

  @Get('profile')
  @Roles('admin', 'editor') // Only users with 'admin' or 'editor' roles can access this route
  getProfile(): string {
    return 'User Profile - Admin/Editor Only';
  }

  @Get('public')
  getPublic(): string {
    return 'Public Route - No Authentication Required';
  }
} 

Route-Level Guard

 import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth/auth.guard';
import { RolesGuard } from './auth/roles.guard';
import { Roles } from './auth/roles.decorator';

@Controller('items')
export class ItemsController {

  @Get()
  @UseGuards(AuthGuard) // Only authenticated users can access this route
  getItems(): string {
    return 'List of Items';
  }

  @Get(':id')
  getItem(@Param('id') id: string): string {
    return `Item ${id}`;
  }
} 

**Explanation:**

  • `@UseGuards(AuthGuard, RolesGuard)` applies the specified guards to the controller or route. Guards are executed in the order they are listed.
  • The `@Roles('admin', 'editor')` decorator is applied to the `getProfile` route, indicating that only users with the 'admin' or 'editor' role are allowed to access it.

5. Registering the Guards

Make sure your guards (and the necessary services like `JwtService`) are registered in a module. Typically, you'll register your `JwtService` in a specific `AuthModule`, import that into the module where your guard needs it, and make sure the guard itself is also added to the `providers` array. If your guard is used globally, add it to the `providers` array in your main `AppModule`: ```typescript import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { AuthModule } from './auth/auth.module'; // Import the AuthModule import { APP_GUARD } from '@nestjs/core'; import { AuthGuard } from './auth/auth.guard'; @Module({ imports: [AuthModule], // Import the AuthModule controllers: [AppController], providers: [ AppService, { provide: APP_GUARD, useClass: AuthGuard, }, ], }) export class AppModule {} ``` **Be careful using a global AuthGuard. It will apply to EVERY endpoint in your app.** This is usually not desired. A more typical and preferred method is to import your guard into each module that will use it.

Conclusion

Guards are a powerful and flexible mechanism for implementing authentication and authorization in NestJS applications. By using guards, you can ensure that your routes are protected and that only authorized users can access sensitive data and functionality. Remember to design your authentication and authorization strategies carefully to meet the specific security requirements of your application.