Using JWT for Authentication in NestJS

@rnab
4 min readDec 30, 2024

--

Authentication is a critical aspect of building secure applications. JSON Web Tokens (JWT) have become a de-facto standard for this purpose, offering a compact and self-contained way to represent authentication and authorization information. NestJS, a progressive Node.js framework for building efficient, reliable, and scalable server-side applications, seamlessly integrates with JWT. In this article, we’ll delve into how to implement JWT-based authentication in a NestJS application using TypeScript, ensuring your application is both secure and scalable.

Prerequisites

Before diving into the implementation, ensure you have the following:

  • Basic knowledge of NestJS and TypeScript
  • Node.js installed on your machine
  • NestJS CLI installed globally

Getting Started

Let’s start by setting up a new NestJS project:

$ nest new jwt-authentication
$ cd jwt-authentication

Install the necessary dependencies for JWT and Passport.js (authentication middleware):

$ npm install @nestjs/jwt @nestjs/passport passport passport-jwt

Setting Up Modules

We will create an AuthModule to encapsulate all authentication-related logic. We'll also need a UsersModule to manage user-related data.

Auth Module

Create the files necessary for the AuthModule:

$ nest g module auth
$ nest g service auth
$ nest g controller auth

Users Module

Similarly, create the files necessary for the UsersModule:

$ nest g module users
$ nest g service users
$ nest g controller users

Implementing JWT Strategy

Create a new file jwt.strategy.ts under the src/auth directory:

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { jwtConstants } from './constants';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}

async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}

We’ll define jwtConstants in a new file constants.ts under src/auth:

export const jwtConstants = {
secret: 'yourSecretKey', // Replace with a strong secret
};

AuthService Implementation

Edit the src/auth/auth.service.ts to include JWT token generation and validation logic:

import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';

@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}

async validateUser(username: string, pass: string): Promise<any> {
const user = await this.usersService.findOne(username);
if (user && user.password === pass) {
const { password, ...result } = user;
return result;
}
return null;
}

async login(user: any) {
const payload = { username: user.username, sub: user.userId };
return {
access_token: this.jwtService.sign(payload),
};
}
}

UsersService Implementation

For simplicity, we’ll use an in-memory store for user data. Adjust src/users/users.service.ts:

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

export type User = any;

@Injectable()
export class UsersService {
private readonly users: User[] = [
{
userId: 1,
username: 'john',
password: 'changeme',
},
{
userId: 2,
username: 'chris',
password: 'secret',
},
{
userId: 3,
username: 'maria',
password: 'guess',
},
];

async findOne(username: string): Promise<User | undefined> {
return this.users.find(user => user.username === username);
}
}

Wiring Everything Together

Edit src/auth/auth.module.ts to integrate all the parts:

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';

@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '60m' },
}),
],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}

Also, edit src/users/users.module.ts:

import { Module } from '@nestjs/common';
import { UsersService } from './users.service';

@Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}

AuthController Implementation

Edit src/auth/auth.controller.ts to handle login requests:

import { Controller, Request, Post, UseGuards } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LocalAuthGuard } from './local-auth.guard';

@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}

@UseGuards(LocalAuthGuard)
@Post('login')
async login(@Request() req) {
return this.authService.login(req.user);
}
}

LocalAuthGuard Implementation

Create a local-auth.guard.ts in src/auth:

import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
async canActivate(context: ExecutionContext) {
const result = (await super.canActivate(context)) as boolean;
const request = context.switchToHttp().getRequest();
await super.logIn(request);
return result;
}
}

Protecting Routes

To protect routes, simply use the @UseGuards(AuthGuard('jwt')) decorator:

Example:

import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Controller('profile')
export class ProfileController {
@UseGuards(AuthGuard('jwt'))
@Get()
getProfile(@Request() req) {
return req.user;
}
}

Conclusion

JWT is a robust solution for authentication in modern applications. By leveraging NestJS and Passport.js, you can create a scalable and secure authentication system. This article provides a basic implementation to get you started. Ensure to enhance security by using strong secret keys, proper error handling, and secure storage for sensitive information.

Happy coding! 🚀

Note: Always remember to never hard-code your secrets in the source code. Use environment variables or a configuration service to manage sensitive information securely.

Feel free to leave comments and feedback, and share your experiences with implementing JWT in your NestJS applications!

--

--

@rnab
@rnab

Written by @rnab

Typescript, Devops, Kubernetes, AWS, AI/ML, Algo Trading

No responses yet