Second part, authentication based on clean architecture
Published: 2021-10-25 16:53:08

#### _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_

```typescript
export const HASH_COMPARE_REPOSITORY = "HASH_COMPARE_REPOSITORY";

export interface IHashCompare {
    compare: (text: string, verify: string) => Promise<boolean>
}
```

_src/domain/models/gateways/encrypt-repository.ts_

```typescript
export const ENCRYPT_REPOSITORY = "ENCRYPT_REPOSITORY";

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

export namespace IEncrypt {
    export type Result = string;
}
```

We create the service to make the use case logic.

```
scaffold create:service --name=authentication
```

_src/domain/use-cases/authentication-service.ts_

```typescript
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
    }
}
```

_src/domain/use-cases/impl/authentication-service-impl.ts_

```typescript
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;
    }
}
```

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_

```typescript
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);
    }
}
```

_src/infrasctructure/driven-adapters/jwt-adapter.ts_

```typescript
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"});
    }
}
```
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

```typescript
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
    }
]
```

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
```

_src/infrastructure/entry-points/api/authentication-controller.ts_

```typescript
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
        }
    }
}
```

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