11. Implementing Authentication in Microservices with NestJS
Published: 2023-11-07 03:27:58

## Introduction:

In a microservices architecture, securing your services and routes is of paramount importance. One way to achieve this is by implementing authentication. This blog post will guide you through the process of adding JWT-based authentication to your routes in a NestJS-based microservices setup. We'll create a common JWT authentication guard that can be reused across various microservices to protect routes, ensuring that only authenticated users can access them.

---

## Prerequisites:

Before getting started, make sure you have the following prerequisites in place:

1. A basic understanding of NestJS and microservices.
2. NestJS installed.
3. Docker and Docker Compose for containerization.
4. Postman (or any other API testing tool) for testing your routes.

---

## Step 1: Setting up Microservices with NestJS

To enable communication between microservices, you'll need to set up a transport layer. We'll use a TCP-based transport layer for this tutorial. Start by installing the necessary NestJS microservices package:

```
npm install @nestjs/microservices
```

> Once installed, make sure to update the content of your **apps/auth/src/main.ts** file in your authentication service with the following content:

```
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';
import * as cookieParser from 'cookie-parser';
import { Transport } from '@nestjs/microservices';

async function bootstrap() {
  const app = await NestFactory.create(AuthModule);
  const configService = app.get(ConfigService);
  app.connectMicroservice({
    transport: Transport.TCP,
    options: {
      host: '0.0.0.0',
      port: configService.get('TCP_PORT'),
    },
  });
  app.use(cookieParser());
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
    }),
  );
  app.useLogger(app.get(Logger));

  await app.startAllMicroservices();
  await app.listen(configService.get('HTTP_PORT'));
}
bootstrap();

```
Our host property  **0.0.0.0**, which tells the microservice to bind to all interfaces on the host, and then for the port, I extract this from the environment. 
we call this environment variable **TCP_PORT**.

I also changed the **PORT** variable for our HTTP server and called this **HTTP_PORT**

> This configuration allows your authentication service to communicate with other services via TCP.

since we are using a new environment variable and renaming the old variable `PORT` to `HTTP_PORT` which is not yet declared in our `auth .env` file let's update our .env file and add the  **HTTP_PORT** and **TCP_PORT**

```
JWT_SECRET=your_secret_key
JWT_EXPIRATION=3600
HTTP_PORT=3001
TCP_PORT=3002
```

Remember that we validated all our environment variables in our auth module so we need also to update our 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';
import { LocalStrategy } from './strategies/local.strategy';
import { JwtStrategy } from './strategies/jwt.strategy';

@Module({
  imports: [
    UsersModule,
    LoggerModule,
    ConfigModule.forRoot({
      isGlobal: true,
      validationSchema: Joi.object({
        JWT_SECRET: Joi.string().required(),
        JWT_EXPIRATION: Joi.string().required(),
        HTTP_PORT: Joi.number().required(),
        TCP_PORT: Joi.number().required(),
      }),
    }),
    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, LocalStrategy, JwtStrategy],
})
export class AuthModule {}

```

---

## Step 2: Creating a Common JWT Auth Guard

The next step is to create a common JWT authentication guard that can be used across different services. This guard will validate JWT tokens passed in requests. To do this, create a new folder in your common directory for auth-related components:

```
mkdir libs/common/src/auth
touch libs/common/src/auth/jwt-auth.guard.ts
touch libs/common/src/auth/index.ts
```

it will create an `auth` directory in your common library then inside that directory it will create two files the `index.ts` and the `jwt-auth.guard.ts`


let's also create a **constants** folder inside our common library in which all our services stored in constant variable will live:

```
mkdir libs/common/src/constants
touch libs/common/src/constants/service.ts
touch libs/common/src/constants/index.ts
```
populate your **constants/service.ts** with the following content: 

```
export const AUTH_SERVICE = 'auth';
```
Also populate your **constants/index.ts** with the following content: 

```
export * from './service';
```

since we created the necessary files and folders we need let's now populate our **auth/jwt-auth.guard.ts** with the following content:

```
import {
  CanActivate,
  ExecutionContext,
  Inject,
  Injectable,
} from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { ClientProxy } from '@nestjs/microservices';
import { Request } from 'express';

import { catchError, map, tap } from 'rxjs/operators';
import { AUTH_SERVICE } from '../constants/service';

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(@Inject(AUTH_SERVICE) private readonly authClient: ClientProxy) {}

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const jwt =
      context.switchToHttp().getRequest().cookies?.Authentication ||
      context.switchToHttp().getRequest()?.Authentication ||
      context.switchToHttp().getRequest().headers?.Authentication;
    console.log('jwt', jwt);

    if (!jwt) {
      return false;
    }

    return this.authClient
      .send('authenticate', {
        Authentication: jwt,
      })
      .pipe(
        tap((res) => {
          context.switchToHttp().getRequest<Request>().user = res;
        }),
        map(() => true),
        catchError(() => of(false)),
      );
  }
}



```

This **JwtAuthGuard** will check for the JWT token in the request's cookies. If a valid token is found, it communicates with the authentication service using the defined pattern and checks if the JWT is valid. If it's valid, it sets the user on the request object and allows the request to proceed.

In the **libs/common/src/auth/index.ts** file export your **jwt-auth.guard.ts** by populating it with this content:

```
//auth/index.ts
export * from './jwt-auth.guard';
```

also in the **libs/common/src/index.ts** file add export all from the auth folder and from constants folder by updating it with this content:

```
export * from './database';
export * from './logger';
export * from './auth';
export * from './constants';

```

---

## Step 3: Adding a Message Pattern for Authentication

In your authentication service, set up a message pattern that corresponds to the authentication flow. In the **apps/auth/src/auth.controller.ts** of your auth service, update your content with the following code:

```
import { Controller, Post, Res, UseGuards } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LocalAuthGuard } from './guards/local.auth-guard';
import { CurrentUser } from './current-user.decorator';
import { UserDocument } from './users/models/user.schema';
import { Response } from 'express';
import { MessagePattern } from '@nestjs/microservices';
import { JwtAuthGuard } from './guards/jwt-auth.guard';

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

  @UseGuards(LocalAuthGuard)
  @Post('login')
  async login(
    @CurrentUser() user: UserDocument,
    @Res({ passthrough: true }) response: Response,
  ) {
    await this.authService.login(user, response);
    response.send(user);
  }

  @UseGuards(JwtAuthGuard)
  @MessagePattern('authenticate')
  async authenticate(@Payload() data: any) {
    return data.user;
  }
}

```

This code sets up a message pattern named `authenticate` that can be used to authenticate JWT tokens we are using are existing JWTAUthGuard to do that. 


We also need to update our **apps/auth/src/strategies/jwt.strategy.ts** because right now we are pulling the JWT off of the request header in the cookies object. However, when the JWT is coming in from our RPC call our JwtAuthGuard, it's not going to be inside of a cookies object.
It's just going to be under the straight request object.

So we can put in a bit of logic here to say if there is no request cookie, we can then check to see if the authentication key actually exists on the request object itself: 


```
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { UsersService } from '../users/users.service';
import { ConfigService } from '@nestjs/config';
import { Tokenpayload } from '../interface/token-payload.interface';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
    configService: ConfigService,
    private readonly userService: UsersService,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromExtractors([
        (request: any) =>
          request?.cookies?.Authentication ||
          request?.Authentication ||
          request?.headers.Authentication,
      ]),
      secretOrKey: configService.get('JWT_SECRET'),
    });
  }

  async validate({ userId }: Tokenpayload) {
    return this.userService.getUser({ _id: userId });
  }
}

```

---

## Step 4: Registering the Authentication Service as a Client

In the reservations service (or any other service that needs authentication), you need to register the authentication service as a client. Modify the reservations.module.ts file as follows:

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

@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(),
      }),
    }),
    ClientsModule.registerAsync([
      {
        name: AUTH_SERVICE,
        useFactory: (configService: ConfigService) => ({
          transport: Transport.TCP,
          options: {
            host: configService.get('AUTH_HOST'),
            port: configService.get('AUTH_PORT'),
          },
        }),
        inject: [ConfigService],
      },
    ]),
  ],
  controllers: [ReservationsController],
  providers: [ReservationsService, ReservationsRepository],
})
export class ReservationsModule {}

```

> This code registers the authentication service as a client using the TCP transport layer.

since we are using two new environment variable which is not yet declared in our `reservations .env` file let's update our .env file and add the  **AUTH_HOST** and **AUTH_PORT**

```
MONGODB_URI=mongodb://mongo:27017/reservation
PORT=3000
AUTH_HOST=auth
AUTH_PORT=3002
```

So the `AUTH_HOST` is actually defined in the `docker-compose.yaml` services by the actual names of the services we defined.

So the hostname for **auth** will be **auth** and the hostname for **reservations** is **reservations** running in Docker.
So we set the auth host here to just be auth.
And the **AUTH_PORT** we set to 3002, which corresponds to the TCP_PORT we define in the auth service.

---

## Step 5: User Extraction and DTO:

**user.dto.ts**
We want to make the user object accessible across our application. For this, we'll define a **Data Transfer Object (DTO)** for the user. This will ensure that the user data is consistent when sent over the network. Here's how you can create the user DTO:

```
mkdir libs/common/src/dto
touch libs/common/src/dto/user.dto.ts
touch libs/common/src/dto/index.ts
```
it will create a `dto` directory in your common library and then inside that directory, it will create two files the `index.ts` and the `user.dto.ts`

populate your `user.dto.ts` with the following content:

```
export interface UserDTO {
  _id: string;
  email: string;
  password: string;
}

```
export your newly created UserDTO in your `index.ts` by populating it with the following code:

```
export * from './user.dto';
```

---

**current-user.decorator.ts**
To access the current user in your routes, you can create a decorators folder. Create a `current-user.decorator.ts` file in the **libs/common/src/decorators** directory:

```
mkdir libs/common/src/decorators
touch libs/common/src/decorators/current-user.decorator.ts
touch libs/common/src/decorators/index.ts
```

it will create a `decorators` directory in your common library and then inside that directory, it will create two files the `index.ts` and the `current-user.decorator.ts`

populate your **current-user.decorator.ts** with this content:

```
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { UserDocument } from 'apps/auth/src/users/models/user.schema';

const getCurrentUserByContex = (ctx: ExecutionContext): UserDocument => {
  return ctx.switchToHttp().getRequest().user;
};
export const CurrentUser = createParamDecorator(
  (_data: unknown, ctx: ExecutionContext) => getCurrentUserByContex(ctx),
);

```
export your newly created CurrentUser  decorator in your `index.ts` by populating it with the following code:

```
export * from './current-user.decorator';
```

to use our user.dto.ts and current-user.decorator of all our services we need to make sure that we export it in our `libs/common/src/index.ts` file: update your **index.ts** file with this content:

```
export * from './database';
export * from './logger';
export * from './auth';
export * from './constants';
export * from './dto';
export * from './decorators';
```

---

We're ready to integrate our newly created common JwtAuthGuard into our reservations routes to lock them down to only authenticated users.

Open your reservation` main.ts` file and update with he following content to use cookie parser middleware:

```
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';
import * as cookieParser from 'cookie-parser';

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

```
Open your reservations service (**reservations.service.ts**) and replace the existing code with the following to dynamically fetch the user ID of the currently authenticated user, instead of hardcoding it.

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

import { ReservationsRepository } from './reservations.repository';
import { CreateReservationDto } from './dto/create-reservation.dto';
import { UpdateReservationDto } from './dto/update-reservation.dto';

@Injectable()
export class ReservationsService {
  constructor(
    private readonly reservationsRepository: ReservationsRepository,
  ) {}

  async create(createReservationDto: CreateReservationDto, userId: string) {
    return this.reservationsRepository.create({
      ...createReservationDto,
      timestamp: new Date(),
      userId,
    });
  }

  async findAll() {
    return this.reservationsRepository.find({});
  }

  async findOne(_id: string) {
    return this.reservationsRepository.findOne({ _id });
  }

  async update(_id: string, updateReservationDto: UpdateReservationDto) {
    return this.reservationsRepository.findOneAndUpdate(
      { _id },
      { $set: updateReservationDto },
    );
  }

  async remove(_id: string) {
    return this.reservationsRepository.findOneAndDelete({ _id });
  }
}

```

Open your reservations controller (**reservations.controller.ts**) and update it with the following content to utilize our common **JwtAuthGuard** and retrieve the currently authenticated user data. We'll use the newly created **CurrentUser** decorator with the **UserDto** type, which is imported from our `@app/common`.

```
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
  UseGuards,
} from '@nestjs/common';
import { ReservationsService } from './reservations.service';
import { CreateReservationDto } from './dto/create-reservation.dto';
import { UpdateReservationDto } from './dto/update-reservation.dto';
import { CurrentUser, JwtAuthGuard, UserDTO } from '@app/common';

@Controller('reservations')
export class ReservationsController {
  constructor(private readonly reservationsService: ReservationsService) {}

  @UseGuards(JwtAuthGuard)
  @Post()
  async create(
    @Body() createReservationDto: CreateReservationDto,
    @CurrentUser() user: UserDTO,
  ) {
    return await this.reservationsService.create(
      createReservationDto,
      user._id,
    );
  }

  @Get()
  @UseGuards(JwtAuthGuard)
  async findAll() {
    return this.reservationsService.findAll();
  }

  @Get(':id')
  @UseGuards(JwtAuthGuard)
  async findOne(@Param('id') id: string) {
    return this.reservationsService.findOne(id);
  }

  @Patch(':id')
  @UseGuards(JwtAuthGuard)
  async update(
    @Param('id') id: string,
    @Body() updateReservationDto: UpdateReservationDto,
  ) {
    return this.reservationsService.update(id, updateReservationDto);
  }

  @Delete(':id')
  @UseGuards(JwtAuthGuard)
  async remove(@Param('id') id: string) {
    return this.reservationsService.remove(id);
  }
}

```

By adding the @UseGuards(JwtAuthGuard) decorator to your route handlers, you ensure that only authenticated users can access these routes.

---

## Conclusion:

In this tutorial, you learned how to add JWT-based authentication to your microservices architecture with NestJS. By creating a common JWT authentication guard, you can easily secure your routes across different services. This approach provides flexibility and reusability, allowing you to enforce authentication in a microservices environment efficiently.