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:
Node.js and npm
Nest CLI
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:
Register a new user:
curl -X POST http://localhost:3000/auth/register -H "Content-Type: application/json" -d '{"username": "testuser", "password": "testpassword"}'Login with the registered user:
curl -X POST http://localhost:3000/auth/login -H "Content-Type: application/json" -d '{"username": "testuser", "password": "testpassword"}'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)