Authentication with Aws Cognito, Passport and NestJs (Part III)
Tags: aws, tutorial, typescript, webdev
Published: 2022-10-14 15:57:31

## Protecting the endpoint

For authentication, we will be using the JWT strategy. We will require the use of a bearer token generated by Cognito for accessing the protected endpoint resources. 

Let's first import the `PassportModule` and give the JWT strategy as default.

####auth.module.ts
```javascript
...
import { PassportModule } from '@nestjs/passport';

@Module({
 imports: [PassportModule.register({ defaultStrategy: 'jwt' })],
 	...
})
export class AuthModule {}

```

Now we create a file called `jwt.strategy.ts` in the same directory as the `auth.controller.ts`.

This class will extend from `PassportStrategy` and pass the chosen strategy. Also, it will verify if the request is valid through the `validate()` callback. We also use a new environment var `AWS_COGNITO_AUTHORITY` that should be `https://cognito-idp.YOUR_POOL_REGION.amazonaws.com/AWS_COGNITO_USER_POOL_ID`, my pool region is North Virginia, so is`us-east-1`.
Don't forget to list it in the `auth.module.ts` providers since it is an `@Injectable`

####jwt.strategy.ts
```javascript

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { passportJwtSecret } from 'jwks-rsa';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      audience: process.env.AWS_COGNITO_COGNITO_CLIENT_ID,
      issuer: process.env.AWS_COGNITO_AUTHORITY,
      algorithms: ['RS256'],
      secretOrKeyProvider: passportJwtSecret({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: process.env.AWS_COGNITO_AUTHORITY + '/.well-known/jwks.json',
      }),
    });
  }

  async validate(payload: any) {
    return { idUser: payload.sub, email: payload.email };
  }
}

```

Finally, we protect the pokemon's list endpoint using a nest guard. Let's head to our `pokemon.controller.ts` and add the `@UseGuards()` decorator passing `AuthGuard('jwt')` as a parameter.
Something like this:

####pokemon.controller.ts
```javascript
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
...

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

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


```

Now, If we don't pass the bearer token produced when we log in, we will get a `401 Unauthorized` from the endpoint: 

![Unauthorized request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m3e1526k8ohutlbqos38.png)

![Authorized request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e8wjiq7ezrj61rf6q2rk.png)

And our endpoint is now protected from unwanted access.


##Changing the password
To change the password, we will need to do another method called `changeUserPassword` in our `aws-cognito.service.ts`. This method will receive another DTO to get our current and new passwords. In that method, Cognito will need to authenticate the user first for changing the password later using the `changePassword` method:

####auth-change-password-user.dto.ts
```javascript
import { IsEmail, Matches } from 'class-validator';

export class AuthChangePasswordUserDto {
  @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' },
  )
  currentPassword: string;

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


```

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

import { AuthChangePasswordUserDto } from './dtos/auth-change-password-user.dto';

...

async changeUserPassword(
    authChangePasswordUserDto: AuthChangePasswordUserDto,
  ) {
    const { email, currentPassword, newPassword } = authChangePasswordUserDto;

    const userData = {
      Username: email,
      Pool: this.userPool,
    };

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

    const userCognito = new CognitoUser(userData);

    return new Promise((resolve, reject) => {
      userCognito.authenticateUser(authenticationDetails, {
        onSuccess: () => {
          userCognito.changePassword(
            currentPassword,
            newPassword,
            (err, result) => {
              if (err) {
                reject(err);
                return;
              }
              resolve(result);
            },
          );
        },
        onFailure: (err) => {
          reject(err);
        },
      });
    });
  }
  
  ...

```

After we only need to do a new route in the `auth.controller` passing the `AuthChangePasswordUserDto` and calling the `changeUserPassword` from the `aws.cognito.service.ts`:

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

@Post('/change-password')
  @UsePipes(ValidationPipe)
  async changePassword(
    @Body() authChangePasswordUserDto: AuthChangePasswordUserDto,
  ) {
    await this.awsCognitoService.changeUserPassword(authChangePasswordUserDto);
  }
  
 ...

```

After the tests, all seems to be working correctly:
![Change password with error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hbg3g3qrr6pb3n2as2i5.png)
![Change Password](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1vmcfjqs5n0icqzkx218.png)
![Login with changed password](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1jwrcu499kfnip9f1gvg.png)

##Reset forgotten password

For resetting forgotten passwords, we will need two new endpoints, one for asking for a unique code and the other for switching the password.
We start by defining two new DTO's:

###auth-forgot-password-user.dto.ts
```javascript
...

import { IsEmail } from 'class-validator';

export class AuthForgotPasswordUserDto {
  @IsEmail()
  email: string;
}
  
 ...

```

###auth-confirm-password-user.dto.ts
```javascript
...

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

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

  @IsString()
  confirmationCode: string;

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

```

Also, we will need to make two new methods in the `aws.service.ts`, to ask Cognito for a new reset code and to change the password:

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

import { AuthConfirmPasswordUserDto } from './dtos/auth-confirm-password-user.dto';
import { AuthForgotPasswordUserDto } from './dtos/auth-forgot-password-user.dto';

...

  async forgotUserPassword(
    authForgotPasswordUserDto: AuthForgotPasswordUserDto,
  ) {
    const { email } = authForgotPasswordUserDto;

    const userData = {
      Username: email,
      Pool: this.userPool,
    };

    const userCognito = new CognitoUser(userData);

    return new Promise((resolve, reject) => {
      userCognito.forgotPassword({
        onSuccess: (result) => {
          resolve(result);
        },
        onFailure: (err) => {
          reject(err);
        },
      });
    });
  }

  async confirmUserPassword(
    authConfirmPasswordUserDto: AuthConfirmPasswordUserDto,
  ) {
    const { email, confirmationCode, newPassword } = authConfirmPasswordUserDto;

    const userData = {
      Username: email,
      Pool: this.userPool,
    };

    const userCognito = new CognitoUser(userData);

    return new Promise((resolve, reject) => {
      userCognito.confirmPassword(confirmationCode, newPassword, {
        onSuccess: () => {
          resolve({ status: 'success' });
        },
        onFailure: (err) => {
          reject(err);
        },
      });
    });
  }
  
  ...

```

Pretty much the same situation on the controller where we add two new routes for the API, calling the respective service methods:

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

  @Post('/forgot-password')
  @UsePipes(ValidationPipe)
  async forgotPassword(
    @Body() authForgotPasswordUserDto: AuthForgotPasswordUserDto,
  ) {
    return await this.awsCognitoService.forgotUserPassword(
      authForgotPasswordUserDto,
    );
  }

  @Post('/confirm-password')
  @UsePipes(ValidationPipe)
  async confirmPassword(
    @Body() authConfirmPasswordUserDto: AuthConfirmPasswordUserDto,
  ) {
    return await this.awsCognitoService.confirmUserPassword(
      authConfirmPasswordUserDto,
    );
  }
 ...

```

Now let's test the new endpoints:
![Forgot Password](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vcvk8ffutpa987e8u8ee.png)

![Password reset email](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1g5ta5x1zdf0n6s3f3oc.png)

![Email confirmation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lhorx7ox3bh86fq8t9oc.png)

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

Boom!! Everything seems to be working flawlessly. 
That's all, folks.
I hope that this series helps you to understand better the Cognito and javascript integration. And you can find the repo [here](https://github.com/fstbraz/authentication-with-aws-cognito-passport-and-nestjs).

Follow me on [Dev](https://dev.to/fstbraz), [Medium](https://medium.com/@fstbraz), [Linkedin](https://www.linkedin.com/in/fstbraz/) or [Twitter](https://twitter.com/fstbraz) to read more about my tech journey.