Nest JS: Understanding and using JWT token for route security | 2024
→ In today’s article we will understand all about JWT Token. We will then use it in our Nest.js application
→ Nest.js is a Node JS framework like Express.js
What is a JWT Token:
→ JWT token is in applications for security routes against attacks.
→ It is open source Industry standard (RFC-7519)
→ It is used for the secure exchange of data between parties.
What does JWT Token look like?
JWT Token is divided into three parts:
Header — Contains metadata about the token(type, hashing algorithm)
Payload — contains claims i.e. statements about entity eg. User info
Signature — Is a result of the encoded header, encoded body, and signed against a secret
Eg:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Check the official website here.
Why is it widely used when storing passwords in DB?
→ When a real John Doe comes at the time of signup after login he can access all his routes i.e. app pages
→ Suppose a fake John Doe comes. He is a regular user and not an admin.
→ If he somehow from Network pulled the payload. He manually changes the roles to admin and tries to access routes that he does not have permissions
→ Fake John Doe will fail as he will not know that other than header and payload — there is a signature (it can be a string or key file) and routes are protected. Hence, the app was saved.
Implementing in Nest.js:
STEP 1: Installing SDK
yarn add @nestjs/jwt @nestjs/passport passport passport-jwt @types/passport-jwt
or
npm install @nestjs/jwt @nestjs/passport passport passport-jwt @types/passport-jwt
STEP 2: Registering JWT Token in Auth Module:
Path: src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [ // REGISTERING PASSPORT AND JWT
PassportModule.register({defaultStrategy: 'jwt'}),
JwtModule.register({
secret: 'topSecret92', // SECRET KEY - TEXT OR FILE
signOptions: {
expiresIn: 3600 // TOKEN EXPIRY TIME
}
}),
TypeOrmModule.forFeature([User])
],
providers: [AuthService, JwtStrategy],
controllers: [AuthController],
exports: [JwtStrategy,PassportModule]
})
export class AuthModule {}
STEP 3: JWT Strategy:
J→ Create a file inside the auth component.
→ On this page we write the logic for getting and validating tokens from the Postman Bearer token
Path: src/auth/jwt.strategy.ts
import { PassportStrategy } from "@nestjs/passport";
import { InjectRepository } from "@nestjs/typeorm";
import { ExtractJwt, Strategy } from "passport-jwt";
import { User } from "./user.entity";
import { Repository } from "typeorm";
import { JwtPayload } from "./jwt-payload.interface";
import { UnauthorizedException } from "@nestjs/common";
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>
) {
super({
secretOrKey: 'topSecret92',
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
});
}
async validate(payload: JwtPayload): Promise<User> {
const { username } = payload;
const user: User = await this.userRepository.findOne({select: ["id", "username", "password"], where: {username}});
if(!user) {
throw new UnauthorizedException();
}
return user;
}
}
→ Once we register JWT, we have access to dependency injection in the auth service
File: AuthService.ts
Path: project/src/auth/auth.service.ts
import {
ConflictException,
Injectable,
InternalServerErrorException,
UnauthorizedException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { AuthCredentialsDto } from './dto/auth-credentials.dto';
import { User } from './user.entity';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt'; // JWT
import { JwtPayload } from './jwt-payload.interface'; // JWT
@Injectable()
export class AuthService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
private jwtService: JwtService
) {}
async signIn(authcredentialsDto: AuthCredentialsDto): Promise<{ accessToken: string }> {
const { username, password } = authcredentialsDto;
const user = await this.userRepository.findOne({ select: ["id", "username", "password"], where: {username} });
if(user && await bcrypt.compare(password, user.password)) {
// PREVIOUSLY HERE ONLY successful message
// JWT TOKEN FOR SECURE
const payload: JwtPayload = { username };
const accessToken = this.jwtService.sign(payload);
return { accessToken };
} else {
throw new UnauthorizedException('Please check your login credentials.');
}
}
}
File: AuthController:
Path: project/src/auth/auth.controller.ts
→ Here we replace the return type string with the object of accessToken
import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthCredentialsDto } from './dto/auth-credentials.dto';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('/signin')
signIn(@Body() authCredentialsDto: AuthCredentialsDto): Promise<{ accessToken: string }> {
return this.authService.signIn(authCredentialsDto);
}
}
→ That’s all about access token registering in Auth
Guarding a Controller of Routes:
→ Now in the Nest.js src file there will be multiple components — functionalities/features of your application.
→ Now if you want to guard your controller, the first step is to import AuthModule in your Nest.js component where you want to guard your route
Eg. Task Component
Path: project/src/tasks/tasks.module.ts
import { Module } from '@nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';
// import { TaskRepository } from './tasks.repository';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Task } from './task.entity';
import { AuthModule } from 'src/auth/auth.module'; // Auth Module Imported
@Module({
imports: [TypeOrmModule.forFeature([Task]), AuthModule], // AUTH Module Added
controllers: [TasksController],
providers: [TasksService],
})
export class TasksModule {}
→ Now go to the controller of your same component
Path: src/tasks/tasks.controller.ts
import {
Body,
Controller,
Get,
Param,
Post,
Delete,
Patch,
Query,
UseGuards,
} from '@nestjs/common';
import { TasksService } from './tasks.service';
import { CreateTaskDto } from './dto/create-task.dto';
import { GetTasksFilterDto } from './dto/get-tasks.dto';
import { UpdateTaskStatusDto } from './dto/update-task-status.dto';
import { Task } from './task.entity';
import { AuthGuard } from '@nestjs/passport'; // IMPORT AUTHGUARD
@Controller('tasks')
@UseGuards(AuthGuard()). // THIS LINE GUARDS YOUR ROUTES
export class TasksController {
constructor(private taskService: TasksService) {}
@Get()
getTasks(@Query() filterDto: GetTasksFilterDto): Promise<Task[]> {
return this.taskService.getTasks(filterDto);
}
@Get('/:id')
getTaskById(@Param('id') id: any): Promise<Task> {
return this.taskService.getTaskById(id);
}
@Delete('/:id')
deleteTaskById(@Param('id') id: string): Promise<void> {
return this.taskService.deleteTaskById(id);
}
@Post()
createTask(@Body() createTaskDto: CreateTaskDto): Promise<Task> {
return this.taskService.createTask(createTaskDto);
}
@Patch('/:id/status')
updateTaskStatus(
@Param('id') id: string,
@Body() updateTaskStatus: UpdateTaskStatusDto
): Promise<Task> {
const { status } = updateTaskStatus;
return this.taskService.updateTaskStatus(id, status);
}
}
Postman:
→ Let us try using Postman:
Running Get All Task API:
API: Get All Tasks:
→ We get an unauthorized error — running without token or expired token
API: Signin API
→ Sign in and generate a JWT token. Copy the token
→ Back to the first request pasting the new token and hitting API
Conclusion:
JWT Token is a way to add extra security to your application. Your backend routes become secure. A token always expires and a fresh login is required for successful operation in apps.
There are many other ways for login for example Microsoft SSO Authenticator, etc. When it comes to storing passwords in a database hashing combined with JWT token is famous and used worldwide across multiple programming languages.
Thank you for reading till the end 🙌 . If you enjoyed this article or learned something new, support me by clicking the share button below to reach more people and/or subscribe Happy Learnings !! to see some other tips, articles, and things I learn about and share there.