Passport JWT Authentication in NestJS

Vishnu C Prasad
Stackademic
Published in
5 min readAug 15, 2023

Passport is a popular authentication middleware for Node.js that provides various authentication strategies, including JWT (JSON Web Token) authentication. NestJS is a powerful framework for building scalable and maintainable server-side applications with TypeScript. In this article, we will explore how to implement JWT authentication using Passport in a NestJS application.

👉 Need a better understanding what a JSON Web Token (JWT) is and how it works? Check out this article:

Prerequisites

Before we begin, make sure you have the following prerequisites installed:

  1. Node.js and npm
  2. Nest CLI
  3. MongoDB

Setting Up the NestJS Project

Let’s start by creating a new NestJS project. Open your terminal and run the following command:

nest new nest-jwt-auth-example

Navigate into the project directory:

cd nest-jwt-auth-example

Installing Dependencies

To implement JWT authentication using Passport and Mongoose, we need to install the necessary packages:

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

Setting Up Mongoose

First, let’s set up Mongoose to connect to your MongoDB database. Open the app.module.ts file and configure the MongooseModule:

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
imports: [
MongooseModule.forRoot('mongodb://127.0.0.1:27017/nest-jwt-auth', {
useNewUrlParser: true,
useUnifiedTopology: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

Replace the connection URL with your MongoDB connection string.

Creating User Schema

Let’s create a user schema using Mongoose. Create a new file named user.schema.ts in a folder named user inside the src directory:

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { ObjectId } from 'mongodb';

@Schema()
export class User {
@Prop({ required: true })
username: string;

@Prop({ required: true })
password: string;
}

export type UserDocument = User & Document;

export const UserSchema = SchemaFactory.createForClass(User);

Creating User Module

Now, create a user module to manage user-related operations. Run the following command to generate a new module, service, and controller:

nest generate module user
nest generate service user
nest generate controller user

In the user.service.ts file, implement methods for user registration and fetching users:

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User, UserDocument } from './user.schema';

@Injectable()
export class UserService {
constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}

async create(username: string, password: string): Promise<User> {
const user = new this.userModel({ username, password });
return user.save();
}

async findByUsername(username: string): Promise<User | null> {
return this.userModel.findOne({ username }).exec();
}

async findById(id: string): Promise<User | null> {
return this.userModel.findById(id).exec();
}
}

Setting Up Passport and JWT Strategy

Next, let’s configure Passport and set up the JWT strategy. Open the auth.module.ts file and create the JWT strategy:

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from './jwt.strategy';
import { AuthService } from './auth.service';
import { UserModule } from '../user/user.module';

@Module({
imports: [
PassportModule,
JwtModule.register({
secret: 'yourSecretKey', // Replace with your own secret key
signOptions: { expiresIn: '1h' }, // Token expiration time
}),
UserModule,
],
providers: [AuthService, JwtStrategy],
exports: [JwtModule],
})
export class AuthModule {}

Implementing JWT Strategy

Create a file named jwt.strategy.ts in the auth folder and implement the JWT strategy:

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { UserService } from '../user/user.service';
import { User } from '../user/user.schema';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private userService: UserService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'yourSecretKey', // Replace with your own secret key
});
}

async validate(payload: any): Promise<User> {
const user = await this.userService.findById(payload.sub);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}

Implementing Auth Service

Create an auth folder inside the src directory. Inside the auth folder, create auth.service.ts:

import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UserService } from '../user/user.service';
import { User } from '../user/user.schema';

@Injectable()
export class AuthService {
constructor(
private userService: UserService,
private jwtService: JwtService,
) {}

async validateUser(username: string, password: string): Promise<User | null> {
const user = await this.userService.findByUsername(username);
if (user && user.password === password) {
return user;
}
return null;
}

async login(user: User): Promise<{ accessToken: string }> {
const payload = { sub: user._id };
return {
accessToken: this.jwtService.sign(payload),
};
}
}

Creating Auth Controller

Generate an auth controller by running the following command:

nest generate controller auth

In the auth.controller.ts file, implement the authentication endpoints:

import { Controller, Post, Request, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';

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

@Post('login')
async login(@Request() req): Promise<any> {
const user = req.user;
const token = await this.authService.login(user);
return { token };
}

@Post('register')
async register(@Request() req): Promise<any> {
const { username, password } = req.body;
const user = await this.authService.register(username, password);
return { user };
}
}

Protecting Routes with AuthGuard

To protect routes with JWT authentication, use the AuthGuard provided by Passport. Open the app.controller.ts file and add the UseGuards decorator:

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

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

Testing the Endpoints

Start your NestJS application:

npm run start

Now you can use tools like curl, Postman, or any API testing tool to test the authentication endpoints. Here's how you can test the endpoints using curl:

1. Register a new user:

curl -X POST http://localhost:3000/auth/register -H "Content-Type: application/json" -d '{"username": "testuser", "password": "testpassword"}'

2. Login with the registered user:

curl -X POST http://localhost:3000/auth/login -H "Content-Type: application/json" -d '{"username": "testuser", "password": "testpassword"}'

3. Use the obtained token to access the protected route:

curl http://localhost:3000/profile -H "Authorization: Bearer <your-token>"

Conclusion

In this article, we’ve learned how to implement JWT authentication using Passport in a NestJS application with a Mongoose database. We covered setting up Mongoose, creating a user schema, configuring Passport and JWT strategy, and protecting routes using the AuthGuard. This approach provides a secure and efficient way to handle user authentication and authorization in your NestJS application.

Here’s a link to a sample project repository for a more concrete demonstration of the concepts discussed in this article: Github Repo

Stackademic 🎓

Thank you for reading until the end. Before you go:

Published in Stackademic

Stackademic is a learning hub for programmers, devs, coders, and engineers. Our goal is to democratize free coding education for the world.

Written by Vishnu C Prasad

As a dedicated software developer, I specialize in utilizing Flutter with Nest.js to create efficient and effective solutions.

No responses yet

Write a response