Introduction
Before diving into frontend authentication, it’s essential to establish a strong foundation by implementing Role-Based Access Control (RBAC) on the backend. This approach ensures that the basics of authentication and RBAC are developed simultaneously, providing a secure and efficient authentication system. This article guides you through the steps to create roles, guards, and necessary endpoints, as well as updating the frontend to leverage this RBAC setup. Additionally, we will update main.ts to configure CORS and enhance our backend logout feature with the developed Logout interceptor
Role based access on backend
To develop basics of RBAC we have to create Roles decorator Roles guard, create ‘users’ API endpoint for Admin role in UsersController for example and update
Generate Roles decorator
nx g @nx/nest:decorator roles --directory=libs/backend/utils/src/lib/decorators --nameAndDirectoryFormat=as-provided
Update generated Roles
import { SetMetadata } from '@nestjs/common';
import { RoleEnum } from '@mtfs/shared/enums';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: RoleEnum[]) => SetMetadata(ROLES_KEY, roles);
Generate Roles guard
nx g @nx/nest:guard roles --directory=libs/backend/features/src/lib/roles/guards --nameAndDirectoryFormat=as-provided
Update libs/backend/features/src/lib/roles/guards/roles.guard.ts:
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { RoleEnum } from '../role.enum';
import { User } from '../../users/entities/user.entity';
// eslint-disable-next-line @nx/enforce-module-boundaries
import { ROLES_KEY } from '../../../../../utils/src/lib/decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(
context: ExecutionContext
): boolean {
const requiredRoles = this.reflector.getAllAndOverride<RoleEnum[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if(!requiredRoles){
return true;
}
const request = context.switchToHttp().getRequest();
const user = request?.user as User;
return requiredRoles.some((role) => user.role?.name === role);
}
}
We have two Roles User and Admin. To check RolesGuard we set up ‘users’ API endpoint to get access only for Admin role.
Update UsersContoller (libs/backend/features/src/lib/users/users.controller.ts)
import {
Controller,
Get,
UseGuards,
} from '@nestjs/common';
import { UsersService } from './users.service'
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { RoleEnum } from '@mtfs/shared/enums';
import { Roles } from '@mtfs/backend/utils';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../roles/guards/roles.guard';
@Controller('users')
@ApiTags('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
@ApiOperation({summary: 'Get all user'})
@ApiResponse({status: 200, description: 'List of users retrieved successfully'})
@Roles(RoleEnum.Admin)
@UseGuards(JwtAuthGuard, RolesGuard)
findAll() {
return this.usersService.findAll();
}
}
CORS configuration
Also we have to update main.ts to configure CORS:
app.enableCors({
origin: '<http://localhost:4200>',
methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'],
credentials: true,
preflightContinue: false,
optionsSuccessStatus: 204,
allowedHeaders: 'Content-Type, Authorization',
});
Update Logout feature on backend
Generate LogoutGuard
nx g @nx/nest:guard logout \\
--directory=libs/backend/features/src/lib/auth/guards \\
--nameAndDirectoryFormat=as-provided
Update LogoutGuard
import { CanActivate, ExecutionContext, Injectable, Logger, UnauthorizedException } from '@nestjs/common';
import { AuthGuard, IAuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';
import { User } from '../../users/entities/user.entity';
import { AuthService } from '../auth.service';
@Injectable()
export class LogoutGuard extends AuthGuard('jwt-refresh-access-token') implements IAuthGuard {
constructor(
private authService: AuthService
) {
super();
}
private readonly logger = new Logger(LogoutGuard.name)
override canActivate(
context: ExecutionContext
){
this.logger.debug(`${LogoutGuard.name} canActivate`);
return super.canActivate(context);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
override handleRequest(err: unknown, user: User): any {
this.logger.debug(`${LogoutGuard.name} handleRequest`);
if (err || !user) {
console.log(err);
console.log(user);
throw err || new UnauthorizedException();
}else {
this.logger.debug(`${LogoutGuard.name} removeCurrentRefreshToken`);
this.authService.removeCurrentRefreshToken(user.id);
}
return user;
}
}
update AuthController
@Get('logout')
@ApiOperation({ summary: 'Log out and remove refresh token' })
@ApiResponse({ status: 200, description: 'Logout successful' })
@UseGuards(LogoutGuard)
@HttpCode(HttpStatus.OK)
@UseInterceptors(ClearCookiesInterceptor)
async logout(@CurrentUser() user: User) {
console.log('LOGOUT')
if (user) {
this.authService.removeCurrentRefreshToken(user.id);
}
return { succes: true };
}
Summary and Conclusion
In this article, we covered simpe way of the implementation of Role-Based Access Control (RBAC) in a NestJS backend and its integration with a frontend application. We began by creating a Roles decorator and a Roles guard to handle role-based access on the backend. We set up an API endpoint in the UsersController to restrict access to users with the Admin role and updated the main.ts file to configure CORS for secure communication between the frontend and backend.
Additionally, we developed a LogoutGuard to handle the logout process more securely. The LogoutGuard ensures that the refresh token is removed when a user logs out, enhancing the security of our application.
By following these steps, we successfully integrated RBAC into both the backend and frontend, creating a more secure and robust authentication system. This comprehensive approach ensures that only authorized users can access specific parts of the application, improving the overall security and user experience.
Now, we are ready back to the fronted app to develop frontend authentication with RBAC.
Discover more from More Than Fullstack
Subscribe to get the latest posts sent to your email.