DEV Community

Vishnu C Prasad
Vishnu C Prasad

Posted on • Originally published at vishnucprasad.Medium

Passport JWT Authentication in NestJS

Passport JWT Authentication in NestJS

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.

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
Enter fullscreen mode Exit fullscreen mode

Navigate into the project directory:

cd nest-jwt-auth-example
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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),
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

Creating Auth Controller

Generate an auth controller by running the following command:

nest generate controller auth
Enter fullscreen mode Exit fullscreen mode

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 };
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

Testing the Endpoints

Start your NestJS application:

npm run start
Enter fullscreen mode Exit fullscreen mode

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 "

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

Top comments (0)