Authentication with Aws Cognito, Passport and NestJs (Part II)
Tags: aws, tutorial, typescript, webdev
Published: 2022-10-13 11:34:21


## Setting up our Nest Js project
Let's start by booting a new instance of a nest js project with `nest new pokemon-app`. 

We also need to install some extra libraries: 

* To be able to communicate with the Cognito service, [Amazon Cognito Identity SDK] (https://www.npmjs.com/package/amazon-cognito-identity-js)
* For validating request body parameters with conditions, [class-validator](https://www.npmjs.com/package/class-validator) 
* Enabling serialize/deserialize objects based on some criteria, [class-transformer](https://www.npmjs.com/package/class-transformer)
* To get the signing keys for the JWT tokens [jwks-rsa](https://www.npmjs.com/package/jwks-rsa)
* [Passport](https://www.npmjs.com/package/passport) to act as auth middleware to authenticate requests
* Passport strategy for JWT [passport-jwt](https://www.npmjs.com/package/passport-jwt)
* Passport utilities for nestjs [@nestjs/passport](https://www.npmjs.com/package/@nestjs/passport)
* Passport jwt types [@types/passport-jwt](https://www.npmjs.com/package/@types/passport-jwt)

####package.json:

```javascript
...

 "dependencies": {
    "@nestjs/common": "^9.0.0",
    "@nestjs/config": "^2.2.0",
    "@nestjs/core": "^9.0.0",
    "@nestjs/platform-express": "^9.0.0",
    "amazon-cognito-identity-js": "^5.2.10",
    "@nestjs/passport": "^9.0.0",
    "@types/passport-jwt": "^3.0.7",
    "class-transformer": "^0.5.1",
    "class-validator": "^0.13.2",
    "jwks-rsa": "^2.1.5",
    "passport": "^0.6.0",
    "passport-jwt": "^4.0.0",
    "reflect-metadata": "^0.1.13",
    "rimraf": "^3.0.2",
    "rxjs": "^7.2.0"
  }

```
Also, to manage environment files in nestjs we need to install the nest config module via `npm i @nestjs/config`. 
For this guide, I will keep it simple and reference them as global, and instead of dependency injection, I will use the `process.env` directly. You can find more documentation [here](https://docs.nestjs.com/techniques/configuration#custom-env-file-path). 

Don't forget to create the `.development.env` file in your root folder.

####app-module.ts:
```javascript
...
@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: '.development.env',
      isGlobal: true,
    }),
    PokemonModule,
    AuthModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

```
<br/>


## Registration and authentication

After, let's navigate to the root directory and boot a module with `nest g module pokemon`, and create a controller and one service with `nest g controller pokemon` and `nest g service pokemon`

Now let's create a pokemon interface under the pokemon directory and export it:

####pokemon.interface.ts:
```javascript

export interface Pokemon {
  readonly name: string;
  readonly type: string;
} 

```

After let's create the method in the service to list the pokemons. (It will be static to have an example of an endpoint to fetch)

####pokemon.service.ts:
```javascript

@Injectable()
export class PokemonService {
  listAllPokemons(): Array<Pokemon> {
    return [
      { name: 'Pikachu', type: 'Electric' },
      { name: 'Volpix', type: 'Fire' },
      { name: 'Flabébé', type: 'Fairy' },
    ];
  }
}

```

Let's reference the endpoint path in the pokemon controller and inject the `PokemonService` to be consumed:

####pokemon.controller.ts:
```javascript
...

import { PokemonService } from './pokemon.service';

@Controller('api/v1/pokemons')
export class PokemonController {
  constructor(private readonly pokemonService: PokemonService) {}

  @Get()
  listAllPokemons(): Array<Pokemon> {
    return this.pokemonService.listAllPokemons();
  }
}

```

We can run `npm run start:dev`. If everything builds fine, we should list the pokemon's at `http://localhost:3000/api/v1/pokemons`

![Listing pokemons](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/10v6po5xx3bjwayw4jqc.png)<br/>

Now we will build our auth module only to allow authenticated users to fetch the info from that endpoint. Let's generate another module with `nest g module auth` and another controller with `nest g controller auth`.

Next, let's create our [DTOs](https://en.wikipedia.org/wiki/Data_transfer_object), under the auth folder, for the user registration and login:  

####auth-login-user:

```javascript
...

import { IsEmail, Matches } from 'class-validator';

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

  /* Minimum eight characters, at least one uppercase letter, one lowercase letter, one number, and one special character */

  @Matches(
    /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$&+,:;=?@#|'<>.^*()%!-])[A-Za-z\d@$&+,:;=?@#|'<>.^*()%!-]{8,}$/,
    { message: 'invalid password' },
  )
  password: string;
}

```

####auth-register-user:
```javascript
...

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

export class AuthRegisterUserDto {
  @IsString()
  name: string;

  @IsEmail()
  email: string;

  /* Minimum eight characters, at least one uppercase letter, one lowercase letter, one number, and one special character */

  @Matches(
    /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$&+,:;=?@#|'<>.^*()%!-])[A-Za-z\d@$&+,:;=?@#|'<>.^*()%!-]{8,}$/,
    { message: 'invalid password' },
  )
  password: string;

}

```

After let's write the Cognito service with the register and authentication methods, generate a new service inside the auth module, and don't forget to reference it in that provider's module:

####aws-cognito.service.ts:
```javascript

import { Injectable } from '@nestjs/common';
import {
  AuthenticationDetails,
  CognitoUser,
  CognitoUserAttribute,
  CognitoUserPool,
} from 'amazon-cognito-identity-js';
import { AuthLoginUserDto } from './dtos/auth-login-user.dto';
import { AuthRegisterUserDto } from './dtos/auth-register-user.dto';

@Injectable()
export class AwsCognitoService {
  private userPool: CognitoUserPool;

  constructor() {
    this.userPool = new CognitoUserPool({
      UserPoolId: process.env.AWS_COGNITO_USER_POOL_ID,
      ClientId: process.env.AWS_COGNITO_CLIENT_ID,
    });
  }

  async registerUser(authRegisterUserDto: AuthRegisterUserDto) {
    const { name, email, password } = authRegisterUserDto;

    return new Promise((resolve, reject) => {
      this.userPool.signUp(
        email,
        password,
        [
          new CognitoUserAttribute({
            Name: 'name',
            Value: name,
          }),
        ],
        null,
        (err, result) => {
          if (!result) {
            reject(err);
          } else {
            resolve(result.user);
          }
        },
      );
    });
  }

  async authenticateUser(authLoginUserDto: AuthLoginUserDto) {
    const { email, password } = authLoginUserDto;
    const userData = {
      Username: email,
      Pool: this.userPool,
    };

    const authenticationDetails = new AuthenticationDetails({
      Username: email,
      Password: password,
    });

    const userCognito = new CognitoUser(userData);

    return new Promise((resolve, reject) => {
      userCognito.authenticateUser(authenticationDetails, {
        onSuccess: (result) => {
         resolve({
            accessToken: result.getAccessToken().getJwtToken(),
            refreshToken: result.getRefreshToken().getToken(),
          });
        },
        onFailure: (err) => {
          reject(err);
        },
      });
    });
  }
}

```

Now we should save in the `development.env` the values for `AWS_COGNITO_USER_POOL_ID` and `AWS_COGNITO_CLIENT_ID` that we held in the first part of the guide.

In our `auth.controller.ts`, we can now inject the AWS Cognito service and register and authenticate the user:

####auth.controller.ts
```javascript

import {
  Body,
  Controller,
  Post,
  UsePipes,
  ValidationPipe,
} from '@nestjs/common';
import { AwsCognitoService } from './aws-cognito.service';
import { AuthLoginUserDto } from './dtos/auth-login-user.dto';
import { AuthRegisterUserDto } from './dtos/auth-register-user.dto';

@Controller('api/v1/auth')
export class AuthController {
  constructor(private awsCognitoService: AwsCognitoService) {}

  @Post('/register')
  async register(@Body() authRegisterUserDto: AuthRegisterUserDto) {
    return this.awsCognitoService.registerUser(authRegisterUserDto);
  }

  @Post('/login')
  @UsePipes(ValidationPipe)
  async login(@Body() authLoginUserDto: AuthLoginUserDto) {
    return this.awsCognitoService.authenticateUser(authLoginUserDto);
  }
}

```
<br/>

Start your application with `npm run start:dev` and if everything starts up without errors, we can give our registry endpoint a go to see if it's working:

![Register endpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/op5mni5fcwm9aor8m11h.png)

![User in pool](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z3t7ynx9h91sembg0ggx.png)

![Verification email](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mpu80nsm7r0wpc7jaggh.png)

Seem's to be working, the user is created and verified. Let's test the login:

![Login Endpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wqlhkka1onzgrp0ffga3.png)

That's all for the second part; in the third part, we will protect the endpoint and the resources using Passport and Jwt Authentication.
Stay tuned 😊