DEV Community

John Piedrahita
John Piedrahita

Posted on • Edited on

Second part, authentication based on clean architecture

Second part...

In the previous post we did the use case for registering a user. In this occasion we are going to continue developing the other use case which is that the user that is registered can authenticate to enter the application. To generate the token we will use the external library jsonwebtoken.

We must create two interfaces to communicate with the infrastructure layer adapters. One to compare that the password corresponds to the database and the other to generate the token from the service.

src/domain/models/gateways/hash-compare-repository.ts

export const HASH_COMPARE_REPOSITORY = "HASH_COMPARE_REPOSITORY";

export interface IHashCompare {
    compare: (text: string, verify: string) => Promise<boolean>
}
Enter fullscreen mode Exit fullscreen mode

src/domain/models/gateways/encrypt-repository.ts

export const ENCRYPT_REPOSITORY = "ENCRYPT_REPOSITORY";

export interface IEncrypt {
    encrypt: (text: string | number | Buffer) => Promise<IEncrypt.Result>
}

export namespace IEncrypt {
    export type Result = string;
}
Enter fullscreen mode Exit fullscreen mode

We create the service to make the use case logic.

scaffold create:service --name=authentication
Enter fullscreen mode Exit fullscreen mode

src/domain/use-cases/authentication-service.ts

export const AUTHENTICATION_SERVICE = "AUTHENTICATION_SERVICE";

export interface IAuthenticationService {
    auth: (data: IAuthenticationService.Params) => Promise<IAuthenticationService.Result>;
}

export namespace IAuthenticationService {
    export type Params = {
        email: string;
        password: string
    }

    export type Result = {
        accessToken: string;
        name: string
    }
}
Enter fullscreen mode Exit fullscreen mode

src/domain/use-cases/impl/authentication-service-impl.ts

import {Adapter, Service} from "@tsclean/core";
import {IAuthenticationService} from "@/domain/use-cases/authentication-service";
import {CHECK_EMAIL_REPOSITORY, ICheckEmailRepository} from "@/domain/models/gateways/check-email-repository";
import {HASH_COMPARE_REPOSITORY, IHashCompare} from "@/domain/models/gateways/hash-compare-repository";
import {ENCRYPT_REPOSITORY, IEncrypt} from "@/domain/models/gateways/encrypt-repository";

@Service()
export class AuthenticationServiceImpl implements IAuthenticationService {
    constructor(
        @Adapter(ENCRYPT_REPOSITORY) private readonly encrypt: IEncrypt,
        @Adapter(HASH_COMPARE_REPOSITORY) private readonly hashCompare: IHashCompare,
        @Adapter(CHECK_EMAIL_REPOSITORY) private readonly checkEmailRepository: ICheckEmailRepository) {
    }

    async auth(data: IAuthenticationService.Params): Promise<IAuthenticationService.Result> {
        const account = await this.checkEmailRepository.checkEmail(data.email);
        const isValid = await this.hashCompare.compare(data.password, account.password);
        if (isValid) {
            const accessToken = await this.encrypt.encrypt(account.id);

            return {
                accessToken,
                name: account.firstName
            }
        }

        return null;
    }
}
Enter fullscreen mode Exit fullscreen mode

After this we move to the infrasctructure layer and modify the bcrypt adapter and create the adapter for the external library jsonwebtoken.

src/infrasctructure/driven-adapters/bcrypt-adapter.ts

import bcrypt from "bcrypt";
import {IHashCompare} from "@/domain/models/gateways/hash-compare-repository";
import {IHashRepository} from "@/domain/models/gateways/hash-repository";

export class BcryptAdapter implements IHashRepository, IHashCompare {
    private readonly salt: number = 12;

    constructor() {
    }

    async compare(text: string, verify: string): Promise<boolean> {
        return await bcrypt.compare(text, verify);
    }

    async hash(text: string): Promise<string> {
        return await bcrypt.hash(text, this.salt);
    }
}
Enter fullscreen mode Exit fullscreen mode

src/infrasctructure/driven-adapters/jwt-adapter.ts

import jwt from "jsonwebtoken";
import {IEncrypt} from "@/domain/models/gateways/encrypt-repository";

export const secret = "my_secret";

export class JwtAdapter implements IEncrypt {

    async encrypt(text: string | number | Buffer): Promise<IEncrypt.Result> {
        return jwt.sign({account: text}, secret, {expiresIn: "1d"});
    }
}
Enter fullscreen mode Exit fullscreen mode

We must add the adapter to the supplier file so that the dependencies in the main container will be resolved. The updated file should look like this

_src/infrastructure/driven-adapters/providers/index.ts

import {AddUserServiceImpl} from "@/domain/use-cases/impl/add-user-service-impl";
import {AuthenticationServiceImpl} from "@/domain/use-cases/impl/authentication-service-impl";

import {JwtAdapter} from "@/infrastructure/driven-adapters/adapters/jwt-adapter";
import {BcryptAdapter} from "@/infrastructure/driven-adapters/adapters/bcrypt-adapter";
import {UserMongooseRepositoryAdapter} from "@/infrastructure/driven-adapters/adapters/orm/mongoose/user-mongoose-repository-adapter";

import {ADD_USER_REPOSITORY} from "@/domain/models/gateways/add-user-repository";
import {CHECK_EMAIL_REPOSITORY} from "@/domain/models/gateways/check-email-repository";
import {ADD_USER_SERVICE} from "@/domain/use-cases/add-user-service";
import {HASH_COMPARE_REPOSITORY} from "@/domain/models/gateways/hash-compare-repository";
import {AUTHENTICATION_SERVICE} from "@/domain/use-cases/authentication-service";
import {HASH_REPOSITORY} from "@/domain/models/gateways/hash-repository";
import {ENCRYPT_REPOSITORY} from "@/domain/models/gateways/encrypt-repository";


export const adapters = [
    {
        classAdapter: BcryptAdapter,
        key: HASH_REPOSITORY
    },
    {
        classAdapter: UserMongooseRepositoryAdapter,
        key: ADD_USER_REPOSITORY
    },
    {
        classAdapter: UserMongooseRepositoryAdapter,
        key: CHECK_EMAIL_REPOSITORY
    },
    {
        classAdapter: BcryptAdapter,
        key: HASH_COMPARE_REPOSITORY
    },
    {
        classAdapter: JwtAdapter,
        key: ENCRYPT_REPOSITORY
    }
]

export const services = [
    {
        classAdapter: AddUserServiceImpl,
        key: ADD_USER_SERVICE
    },
    {
        classAdapter: AuthenticationServiceImpl,
        key: AUTHENTICATION_SERVICE
    }
]
Enter fullscreen mode Exit fullscreen mode

We create the entry point for authentication and then we must add it to the index.ts to export it to the main container

scaffold create:controller --name=authentication
Enter fullscreen mode Exit fullscreen mode

src/infrastructure/entry-points/api/authentication-controller.ts

import {Mapping, Inject, Post, Body, Adapter} from "@tsclean/core";
import {AUTHENTICATION_SERVICE, IAuthenticationService} from "@/domain/use-cases/authentication-service";
import {ValidateFields} from "@/infrastructure/helpers/validate-fields";

@Mapping('api/v1/authentication')
export class AuthenticationController {

    constructor(
        @Adapter(AUTHENTICATION_SERVICE) private readonly authenticationService: IAuthenticationService
    ) {
    }

    @Post()
    async authController(@Body() data: IAuthenticationService.Params): Promise<IAuthenticationService.Result | any> {

        const {errors, isValid} = ValidateFields.fieldsValidation(data);

        if (!isValid) return {statusCode: 422, body: {"message": errors}}

        const result = await this.authenticationService.auth(data);
        return {
            accessToken: result.accessToken,
            name: result.name
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

If everything went well and you have reached this point when you visit the endpoint http://localhost:9000/api/v1/authentication, it should generate the token.

next third part...

https://dev.to/japhernandez/authentication-based-on-clean-architecture-39np

Top comments (0)