When building robust backend systems, ensuring secure access to your APIs is paramount. One of the common frameworks used today is NestJS, a progressive Node.js framework that makes use of TypeScript and is designed for building efficient and scalable server-side applications. One of the core features of NestJS is its powerful routing and middleware capabilities, which includes Guards.
In this Medium article, we will dive into how you can use Guards in NestJS to protect your routes, ensuring that only authenticated and authorized users can access specific endpoints.
What are Guards?
Guards in NestJS are used to control the flow of incoming requests. They determine whether a request will be handled by the route handler or not. Essentially, Guards provide a way to implement authentication and authorization logic before proceeding to the route handler.
Creating a Guard
To create a guard, you need to implement the CanActivate
interface provided by NestJS. Here is a basic example of a guard that checks for an authorization token in the request headers.
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization;
if (!token || token !== 'VALID_TOKEN') {
throw new UnauthorizedException('Invalid token');
}
return true;
}
}
Applying the Guard to a Route
Once you have defined your guard, you can apply it to your routes. You can do this globally, on a controller, or on a specific route.
Applying Guard Globally
To apply a guard globally, you need to do it in your main module:
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
})
export class AppModule {}
Applying Guard to a Controller or Route
To apply a guard to a specific controller or route, you can use the @UseGuards()
decorator:
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
@Controller('protected')
@UseGuards(AuthGuard)
export class ProtectedController {
@Get()
getProtectedResource() {
return { message: 'This is a protected resource' };
}
}
Advanced: Role-Based Authorization
For more complex scenarios, such as role-based authorization, you can extend the guard to check for user roles. Let’s define a RolesGuard
that checks if the user has the required role to access a route.
First, let’s create a roles guard:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
return roles.some(role => user.roles?.includes(role));
}
}
You can now create a custom decorator to specify roles:
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
Finally, apply it to your controller or routes:
import { Controller, Get, UseGuards } from '@nestjs/common';
import { Roles } from './roles.decorator';
import { RolesGuard } from './roles.guard';
@Controller('admin')
@UseGuards(RolesGuard)
export class AdminController {
@Get()
@Roles('admin')
getAdminResource() {
return { message: 'This is an admin resource' };
}
}
Conclusion
By using guards in NestJS, you can secure your routes efficiently, implementing both authentication and authorization logic seamlessly. They provide a flexible and powerful way to manage access control in your application.
Guards are just one part of the overall security strategy. It’s crucial to keep in mind other security practices, such as input validation, sanitization, and using secure protocols to ensure a comprehensive security posture for your application.
Happy coding! If you have any questions or thoughts, feel free to leave a comment below.