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).
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.