DEV Community

Jayvee Ramos
Jayvee Ramos

Posted on

7. Authentication Module

Introduction:

In our journey to create a scalable microservices architecture, we are now ready to add an authentication microservice to our application. This microservice will handle all authentication and identity access management-related functionalities, allowing us to authenticate users across different parts of our application seamlessly.

Creating the Authentication Microservice:
Let's start by creating the authentication microservice. We will use the NestJS CLI to generate the necessary files and directories.


Step 1: Generate the Authentication Microservice
In your terminal, run the following command to generate the authentication microservice:

nest generate app auth
Enter fullscreen mode Exit fullscreen mode

This command creates a new directory named "auth" and updates the project configuration files accordingly. You can find the newly created "auth" application in the nest-cli.json.


Step 2: Implementing User Functionality
The core of our authentication microservice is handling user functionality, such as user creation and authentication. We want the microservice to be able to create users and authenticate them by providing a JSON Web Token (JWT). Let's start by implementing user creation.

Defining the User DTO:
We need a DTO (Data Transfer Object) to handle the data required for user creation. Create a folder named "DTO" inside the "users" directory to do this run the following command on your terminal:

mkdir apps/auth/src/users/dto
Enter fullscreen mode Exit fullscreen mode

then:

touch apps/auth/src/users/dto/create-user.dto.ts
Enter fullscreen mode Exit fullscreen mode

define a "create-user.dto.ts" file with the following content:

import { IsEmail, IsString, IsStrongPassword } from 'class-validator';

export class CreateUserDTO {
  @IsEmail()
  email: string;

  @IsString()
  @IsStrongPassword()
  password: string;
}


Enter fullscreen mode Exit fullscreen mode

Creating the User Module, Controller and Service:
In the "auth" application, generate a user module using the following commands:

nest generate module users --project auth
nest generate controller users --project auth
nest generate service users --project auth
Enter fullscreen mode Exit fullscreen mode

Defining the User Schema and Repository:

Create a new directory named "models" inside the "users" directory. In this directory, define the user schema in a "user.schema.ts" file:
to do this run the following command on your terminal:

mkdir apps/auth/src/users/models
Enter fullscreen mode Exit fullscreen mode

then:

touch apps/auth/src/users/models/user.schema.ts
Enter fullscreen mode Exit fullscreen mode

then populate the user.schema.ts with the following code:

import { AbstractDocument } from '@app/common';
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';

@Schema({ versionKey: false })
export class UserDocument extends AbstractDocument {
  @Prop({ type: String, required: true })
  email: string;

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

export const UserSchema = SchemaFactory.createForClass(UserDocument);

Enter fullscreen mode Exit fullscreen mode

we need to define user.repository.ts that extends our Abstract repository just like we did on our reservations app
to do this run the following command on your terminal:

touch apps/auth/src/users/user.repository.ts
Enter fullscreen mode Exit fullscreen mode

then populate the it with the following code:

import { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';
import { Injectable, Logger } from '@nestjs/common';
import { AbstractRepository } from '@app/common';
import { UserDocument } from './models/user.schema';

@Injectable()
export class UserRepository extends AbstractRepository<UserDocument> {
  protected readonly logger = new Logger(UserRepository.name);
  constructor(
    @InjectModel(UserDocument.name)
    protected readonly userModel: Model<UserDocument>,
  ) {
    super(userModel);
  }
}

Enter fullscreen mode Exit fullscreen mode

Defining the Users Service:
In your "users.service.ts" file, add the following code to create user:

import { Injectable } from '@nestjs/common';
import { UserRepository } from './user.repository';
import { CreateUserDTO } from '../dto/create-user.dto';

@Injectable()
export class UsersService {
  constructor(private readonly userRepository: UserRepository) {}

  async create(createUserDto: CreateUserDTO) {
    return this.userRepository.create({
      ...createUserDto,
    });
  }
}

Enter fullscreen mode Exit fullscreen mode

Defining the Users Controller:
In your "users.controller.ts" file, add the following code to create user it connects to user.service create method that we just created:

import { Body, Controller, Post } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDTO } from '../dto/create-user.dto';

@Controller('users')
export class UsersController {
  constructor(private readonly userService: UsersService) {}

  @Post()
  async createUser(@Body() createUserDto: CreateUserDTO) {
    return this.userService.create(createUserDto);
  }
}

Enter fullscreen mode Exit fullscreen mode

Update User Module to connect to our Database

Open your "users.module.ts" file, and update the content with the following code:

import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { DatabaseModule, LoggerModule } from '@app/common';
import { UserDocument, UserSchema } from './models/user.schema';
import { UserRepository } from './user.repository';

@Module({
  imports: [
    DatabaseModule,
    DatabaseModule.forFeature([
      { name: UserDocument.name, schema: UserSchema },
    ]),
    LoggerModule,
  ],
  controllers: [UsersController],
  providers: [UsersService, UserRepository],
})
export class UsersModule {}

Enter fullscreen mode Exit fullscreen mode

what we did was we just imported the DatabaseModule and the LoggerModule then add UserRepository in our provider array so that we can use it in our user module


Update User Module to connect to our Database

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';

@Module({
  imports: [UsersModule, LoggerModule],
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule {}

Enter fullscreen mode Exit fullscreen mode

Test our auth setup

to test it run the following command on your terminal:
npm run start:dev auth
if you see this logs

[Nest] 21036  - 10/27/2023, 4:31:48 PM     LOG [NestFactory] Starting Nest application...
[Nest] 21036  - 10/27/2023, 4:31:48 PM     LOG [InstanceLoader] DatabaseModule dependencies initialized +57ms
[Nest] 21036  - 10/27/2023, 4:31:48 PM     LOG [InstanceLoader] MongooseModule dependencies initialized +2ms
[Nest] 21036  - 10/27/2023, 4:31:48 PM     LOG [InstanceLoader] LoggerModule dependencies initialized +1ms
[Nest] 21036  - 10/27/2023, 4:31:48 PM     LOG [InstanceLoader] ConfigHostModule dependencies initialized +1ms
[Nest] 21036  - 10/27/2023, 4:31:48 PM     LOG [InstanceLoader] AuthModule dependencies initialized +0ms
[Nest] 21036  - 10/27/2023, 4:31:48 PM     LOG [InstanceLoader] ConfigModule dependencies initialized +0ms
[Nest] 21036  - 10/27/2023, 4:31:48 PM     LOG [InstanceLoader] LoggerModule dependencies initialized +0ms
[Nest] 21036  - 10/27/2023, 4:31:48 PM     LOG [InstanceLoader] ConfigModule dependencies initialized +3ms
[Nest] 21036  - 10/27/2023, 4:32:18 PM   ERROR [MongooseModule] Unable to connect to the database. Retrying (1)...
Enter fullscreen mode Exit fullscreen mode

then our setup is successful !

Note: we have a mongoose error because we are not yet connected to our database we will do that in the next section by dockerizing out auth module


Integrating the Authentication Microservice with Docker:

Now that we have implemented the core functionalities of our authentication microservice, it's time to Dockerize it. This will allow us to run the microservice alongside other microservices seamlessly.

Dockerizing the Authentication Microservice:
To Dockerize the authentication microservice, we need to create a Dockerfile. In the "auth" directory, create a new "Dockerfile"
to do this run the following command on your terminal:

touch apps/auth/Dockerfile
Enter fullscreen mode Exit fullscreen mode

and populate with the following content:


---

# Use Node Alpine as the base image for development
FROM node:alpine as development

WORKDIR /usr/src/app

COPY package.json package-lock.json ./
RUN npm install -g && npm install

COPY . .

RUN npm run build

# Create a production image
FROM node:alpine as production

ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

WORKDIR /usr/src/app

COPY package.json package-lock.json ./
RUN npm install --only=production

COPY --from=development /usr/src/app/dist ./dist
COPY --from=development /usr/src/app/package.json ./package.json

CMD [ "node", "dist/apps/auth/main" ]
Enter fullscreen mode Exit fullscreen mode

Update Docker Compose:
In your project's root directory, our existing "docker-compose.yaml" file let's define our auth service.

update your existing code with the following content:

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

Enter fullscreen mode Exit fullscreen mode

This configuration specifies the build context and Dockerfile for the authentication microservice, sets the target to "development," and specifies the command to run the microservice. It also maps Port 3001 in the container to Port 3001 on the host.


Update auth main.ts file
Since we have declared in our Docker service that our auth service should run on port 3001, we also need to declare it in our apps/auth/main.ts open it then populate it with the following code:

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

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


Enter fullscreen mode Exit fullscreen mode

Testing the Authentication Microservice with Docker:

With both the User and Authentication microservices Dockerized, you can now run them using Docker Compose. In your project's root directory, run the following command:

docker-compose up
Enter fullscreen mode Exit fullscreen mode

Docker Compose will start both microservices and a MongoDB container, allowing them to communicate with each other.


Testing User Creation:

Now, you can test the user creation functionality of the authentication microservice. Use a tool like Postman or your preferred API client to send a POST request to the following URL:

http://localhost:3001/users
Enter fullscreen mode Exit fullscreen mode

Ensure you provide a valid JSON payload with an email and a strong password in the request body. For example:

{
  "email": "jayvee@xample.com",
  "password": "Jayvee2345!"
}
Enter fullscreen mode Exit fullscreen mode

If you receive a 201 Created response, it means the user creation was successful. If there are validation errors, the authentication microservice will provide feedback on what went wrong.


Conclusion:

In this tutorial, we've created an authentication microservice using NestJS and Docker. We've implemented user creation functionality, Dockerized the microservice, and ensured that it can communicate with other microservices using Docker Compose.

By following this guide, you have set the foundation for a scalable and secure authentication system that can be integrated into various parts of your microservices architecture. With this authentication microservice, you can now proceed to implement user authentication and authorization features across your application.

Top comments (0)