DEV Community

Terence Faid JABO
Terence Faid JABO

Posted on

Implementing an Auth Guard with JWT tokens in Nest.js

Image description
Authentication guards allow you to control access to routes and controllers in a NestJS application based on user authentication. An Auth Guard is very similar to middlewares in Express.js

Image description

The Auth Guard which is applied to an endpoint/api or a controller in Nest.js only allows authenticated users to access those routes. A token is attached in the incoming request headers, Auth Guard extracts and validates the token, and uses the extracted information to determine whether to process the request further or not.

In this blog, we will look at how to implement basic authentication guards using JWT tokens to protect routes in a NestJS app.

I have created a basic Nest.js CRUD application for demonstration which looks likes this:

Image description
There is student controller which has basic read/write operations (GET, POST requests) on which we shall apply our auth guard.

Create an auth.guard.ts file and let’s start creating an auth guard. First, lets have the necessary imports

auth.guard.ts

import { CanActivate, ExecutionContext, Injectable, UnauthorizedException, 
ForbiddenException,} from '@nestjs/common';

import { AuthService } from './auth.service';
Enter fullscreen mode Exit fullscreen mode

auth.guard.ts

@Injectable()
export class AuthGuard implements CanActivate {
    async canActivate(context: ExecutionContext): Promise<boolean> {
      try {
        const request = context.switchToHttp().getRequest();
        return true;
      } catch (error) {
        console.log('auth error - ', error.message);
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

Authentication guards are classes that implement the CanActivate interface from @nestjs/common. This interface requires a canActivate method/function that returns a boolean. Here we access the incoming request using context.switchToHttp().getRequest() method.

const { authorization }: any = request.headers;
if (!authorization || authorization.trim() === '') {
    throw new UnauthorizedException('Please provide token');
}
const authToken = authorization.replace(/bearer/gim, '').trim();
Enter fullscreen mode Exit fullscreen mode

We extract the token from Authorization headers in the request. If there is no Authorization header or no token in the Authorization Header, it throws and error to provide token.

const resp = await this.authService.validateToken(authToken);
request.decodedData = resp;
return true;
Enter fullscreen mode Exit fullscreen mode

We have created a function in AuthService to validate our jwt token which if valid then attaches the decodedData i.e payload(user data by which we create our jwt token) to the request and returns true to further process the request. And if token isnt’t valid, then throws an error.

constructor(private readonly authService: AuthService) {}
Enter fullscreen mode Exit fullscreen mode

As we are using AuthService, lets import it as a dependency injection in our AuthGuard’s class constructor.

Let’s have a look at validateToken() function in AuthService.

import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
    constructor(private readonly jwtServ: JwtService) {}

    validateToken(token: string) {
        return this.jwtServ.verify(token, {
            secret : process.env.JWT_SECRET_KEY
        });
    }
}


Enter fullscreen mode Exit fullscreen mode

We are using JwtService from Nest.js and also using it as a dependency injection in our class’s constructor to use its function to validate our jwt token. We use verify() function which takes token and secret key to verify the token. This would be the same secret key to create our jwt token in the sign-in & sign-up API’s. Use a .env file to store our JWT_SECRET_KEY.

auth.guard.ts

import { CanActivate, ExecutionContext, Injectable, UnauthorizedException, 
ForbiddenException,} from '@nestjs/common';
import { AuthService } from './auth.service';

  @Injectable()
  export class AuthGuard implements CanActivate {
    constructor(private readonly authService: AuthService) {}

    async canActivate(context: ExecutionContext): Promise<boolean> {
      try {
        const request = context.switchToHttp().getRequest();
        const { authorization }: any = request.headers;
        if (!authorization || authorization.trim() === '') {
          throw new UnauthorizedException('Please provide token');
        }
        const authToken = authorization.replace(/bearer/gim, '').trim();
        const resp = await this.authService.validateToken(authToken);
        request.decodedData = resp;
        return true;
      } catch (error) {
        console.log('auth error - ', error.message);
        throw new ForbiddenException(error.message || 'session expired! Please sign In');
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

Lets apply our Auth guard to our Api routes.

student.controller.ts

import { AuthGuard } from 'src/auth/auth.guard';

@Controller('student')
export class StudentController {
    constructor(private readonly studentService: StudentService) { }

    @Get()
    async getStudents(@Res() response) {
        try {
            const studentData = await this.studentService.getAllStudents();
            return response.status(HttpStatus.OK).json({
                message: 'All students data found successfully', studentData,
            });
        } catch (err) {
            return response.status(err.status).json(err.response);
        }
    }

    @UseGuards(AuthGuard)
    @Get('/:id')
    async getStudentById(@Res() response, @Param('id') studentId: string) {
        try {
            const existingStudent = await this.studentService.getStudent(studentId);
            return response.status(HttpStatus.OK).json({
                message: 'Student found successfully', existingStudent,
            });
        } catch (err) {
            return response.status(err.status).json(err.response);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

We have 2 routes getStudents() to get all students which is an open route and getStudentById() to get a specific student details which is a protected route. We use @UseGuards(AuthGuard) decorator and pass our AuthGuard to it which protects the getStudentById() route.

We can also attach the AuthGuard to an entire controller rather than specific routes which makes the entire Controller as Protected Controller.

@UseGuards(AuthGuard)
@Controller('student')
Enter fullscreen mode Exit fullscreen mode

Now test your endpoints using postman or thunderclient

Top comments (0)