8. JWT Authentication with NestJS and Passport
Published: 2023-11-07 03:27:18

## Introduction:

In this tutorial, we'll guide you through the process of implementing JWT (JSON Web Token) authentication using NestJS and Passport. JWT authentication is a common method for securing web applications by providing a way to authenticate users and authorize access to specific resources.

By the end of this tutorial, you'll have a working JWT authentication setup for your NestJS application. We will cover installing the necessary dependencies, configuring JWT, and handling environment variables for different microservices.

Let's get started!

---

## Step 1: Installing Dependencies
To begin, we'll install the required dependencies for JWT authentication and Passport. Open your project directory and stop your application if it's running. Then, run the following commands to install the necessary packages:

```
npm install @nestjs/passport passport passport-local
npm install --save-dev @types/passport-local
npm install @nestjs/jwt passport-jwt
npm install --save-dev @types/passport-jwt

```
These packages are essential for setting up JWT authentication and Passport for your NestJS application.

---

## Step 2: Configuring JWT

Now that we have the dependencies in place, it's time to configure JWT for your application. Open your **auth.module.ts** file, and update the content with the following code

```
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { UsersModule } from './users/users.module';
import { LoggerModule } from '@app/common';
import { JwtModule } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';

@Module({
  imports: [
    UsersModule,
    LoggerModule,
    JwtModule.registerAsync({
      useFactory: (configService: ConfigService) => ({
        secret: configService.get<string>('JWT_SECRET'), //JWT_SECRET not yet defined
        signOptions: {
          expiresIn: `${configService.get<string>('JWT_EXPIRATION')}s`, //JWT_EXPIRATION not yet defined
        },
      }),
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule {}

```
In this code, we're setting up the JWT module to use your environment variables, such as JWT_SECRET and JWT_EXPIRATION, which should be securely stored.

---

## Step 3: Managing Environment Variables
To manage environment variables more effectively for different microservices, we'll create separate .env files for each service. This enhances modularity and reduces configuration overlap.

Create a unique .env file for the auth service and move all related environment variables into it. 

to create .env file in your auth module just run the following command on your terminal:

```
touch apps/auth/.env
```
populate it with the following content:

```
JWT_SECRET=your_secret_key
JWT_EXPIRATION=3600
```
Similarly, create a .env file for the reservations service, just run this command

```
touch apps/reservations/.env
```
populate it with the following content:

```
MONGODB_URI=mongodb://mongo:27017/reservation
```

---
## Step 4: Update docker-compose.yml
since we created a .env file for both of our different services we can now tell our docker-compose in which environment each of our services will be using, just update your docker-compose.yml file with the following content 

```
services:
  reservations:
    build:
      context: .
      dockerfile: apps/reservations/Dockerfile
      target: development
    command: npm run start:dev reservations
    env_file:
      - ./apps/reservations/.env
    ports:
      - '3000:3000'
    volumes:
      - .:/usr/src/app
  auth:
    build:
      context: .
      dockerfile: apps/auth/Dockerfile
      target: development
    command: npm run start:dev auth
    env_file:
      - apps/auth/.env
    ports:
      - '3001:3001'
    volumes:
      - .:/usr/src/app
  mongo:
    image: mongo

```

---

## Step 5: Refactoring Configuration

We will start by removing the config directory entirely from lib/common. As a result, we'll only have the database and logger modules left. 
you can delete the config directory in your libs/common/src manually or you can also run the following command on your terminal:

```
rm -r libs/common/src/config
```
This change may initially lead to some issues, as the database module won't find the config module anymore. However, this is part of the plan.

To fix this, remove the config module import in the database module and also from the database module itself. The database module will now depend on the service calling it to set up the config module. (**Update your database module with the following code**)

```
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ModelDefinition, MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [
    MongooseModule.forRootAsync({
      useFactory: (configService: ConfigService) => ({
        uri: configService.get('MONGODB_URI'),
      }),
      inject: [ConfigService],
    }),
  ],
})
export class DatabaseModule {
  static forFeature(models: ModelDefinition[]) {
    return MongooseModule.forFeature(models);
  }
}

```
Now, let's set up the config module directly within each individual microservice. This way, each service can configure the config module specifically for its needs. This approach is more flexible and manageable than our previous setup.

also, remove the config module import on your **libs/common/src/index.ts**

---

## Step 6: Setting Up the config Module for reservations

In each microservice, we'll import the config module from @nestjs/config and register it as global so that the config module is available to anyone who needs it in that service.

Additionally, we can define a validation schema for the required environment variables. For example, we can use the Joi library to define the schema.
update your **reservations.module.ts** with the following content:

```
import { Module } from '@nestjs/common';
import { ReservationsService } from './reservations.service';
import { ReservationsController } from './reservations.controller';
import { DatabaseModule, LoggerModule } from '@app/common';
import { ReservationsRepository } from './reservations.repository';
import {
  ReservationDocument,
  ReservationSchema,
} from './entities/reservation.entity';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';

@Module({
  imports: [
    DatabaseModule,
    DatabaseModule.forFeature([
      { name: ReservationDocument.name, schema: ReservationSchema },
    ]),
    LoggerModule,
    ConfigModule.forRoot({
      isGlobal: true,
      validationSchema: Joi.object({
        MONGODB_URI: Joi.string().required(),
        PORT: Joi.number().required(), //we wil setup this env later
      }),
    }),
  ],
  controllers: [ReservationsController],
  providers: [ReservationsService, ReservationsRepository],
})
export class ReservationsModule {}

```

So now we have this config set up for reservations and you can see
and then we'll do the same thing in the auth.module.ts.

---

## Step 7: Setting Up the config Module for auth
update your **auth.module.ts** with the following content:

```
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { UsersModule } from './users/users.module';
import { LoggerModule } from '@app/common';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import * as Joi from 'joi';

@Module({
  imports: [
    UsersModule,
    LoggerModule,
    ConfigModule.forRoot({
      isGlobal: true,
      validationSchema: Joi.object({
        JWT_SECRET: Joi.string().required(),
        JWT_EXPIRATION: Joi.string().required(),
        PORT: Joi.number().required(), //we wil setup this env later
      }),
    }),
    JwtModule.registerAsync({
      useFactory: (configService: ConfigService) => ({
        secret: configService.get<string>('JWT_SECRET'),
        signOptions: {
          expiresIn: `${configService.get<string>('JWT_EXPIRATION')}s`,
        },
      }),
      inject: [ConfigService],
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule {}


```
Restart your containers to apply the changes, and run the following command on your terminal:

```
docker-compose up
```
you should see no error in your logs

---
## Step 8: Dynamic Port Allocation
Lastly, let's make our microservices even more dynamic by allocating ports dynamically. In the** main.ts file of each service**, you can retrieve the port value from the config service. This allows you to avoid hardcoding port numbers, which is essential when you plan to deploy your applications.

update your **apps/auth/src/main.ts** file with the following code:

```
import { NestFactory } from '@nestjs/core';
import { AuthModule } from './auth.module';
import { ValidationPipe } from '@nestjs/common';
import { Logger } from 'nestjs-pino';
import { ConfigService } from '@nestjs/config';

async function bootstrap() {
  const app = await NestFactory.create(AuthModule);
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
    }),
  );
  app.useLogger(app.get(Logger));
  const configService = app.get(ConfigService);
  await app.listen(configService.get('PORT'));
}
bootstrap();


```
update your **apps/reservations/src/main.ts** file with the following code:

```
import { NestFactory } from '@nestjs/core';
import { ReservationsModule } from './reservations/reservations.module';
import { ValidationPipe } from '@nestjs/common';
import { Logger } from 'nestjs-pino';
import { ConfigService } from '@nestjs/config';

async function bootstrap() {
  const app = await NestFactory.create(ReservationsModule);
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
    }),
  );
  app.useLogger(app.get(Logger));
  const configService = app.get(ConfigService);
  await app.listen(configService.get('PORT'));
}
bootstrap();

```

update your **apps/reservations/.env** file with the following code:

```
MONGODB_URI=mongodb://mongo:27017/reservation
PORT=3000
```
update your **apps/auth/.env** file with the following code:

```
JWT_SECRET=your_secret_key
JWT_EXPIRATION=3600
PORT=3001
```

Congratulations! You've successfully implemented JWT authentication for your NestJS application using Passport and improved your environment variable management for different microservices.