Amazon Cognito is a user authentication and management service that makes it easy to add sign-up, sign-in, and access control to your web and mobile applications. In this blog post, we will explore how to use Amazon Cognito with NestJS to manage user authentication and authorization.

Prerequisites

  • Basic knowledge of NestJS framework.
  • An AWS account with permissions to create a Cognito User Pool and App Client.

Setup

Set up a Cognito User Pool

To use Amazon Cognito with NestJS, we need to first set up a user pool in the AWS console. A user pool is a user directory in Amazon Cognito. It allows users to sign up and sign in to our application.

To create a user pool, navigate to the Amazon Cognito console, and click on Manage User Pools. Then click on Create a user pool button. Fill in the details like pool name, policies, attributes, and other configuration as required. Finally, create the user pool.

Create an App Client

Now that we have created a user pool, we need to create an app client to enable our NestJS application to interact with the user pool. To create an app client, navigate to the App clients tab in the user pool settings, and click on Add an app client button. Fill in the details like app client name, app client settings, and other configuration as required. Finally, create the app client.

Install Required Libraries

To use the Cognito service in our NestJS application, we need to install the following packages:

  • amazon-cognito-identity-js
  • @aws-sdk/client-cognito-identity-provider Run the following command to install the required packages:
npm install amazon-cognito-identity-js @aws-sdk/client-cognito-identity-provider

Configure AWS Cognito in NestJS

We will now create a service to manage user authentication and authorization using Cognito in our NestJS application. Create a new file named aws-cognito-auth.service.ts and add the following code to it:

import { BadRequestException, HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { AwsCognitoAuthConfig } from '../../aws-cognito-auth.config';
import {
    AuthenticationDetails,
    CognitoUser,
    CognitoUserAttribute,
    CognitoUserPool,
    CognitoUserSession,
    ISignUpResult,
} from 'amazon-cognito-identity-js';

import { RegisterRequest } from '../../model/register.request';
import { LoginRequest } from '../../model/login.request';
import {
    AdminDeleteUserCommand,
    CognitoIdentityProviderClient,
    GetUserCommand,
    ChangePasswordCommand,
} from '@aws-sdk/client-cognito-identity-provider';
import { Optional } from '@connect/common-utils';

@Injectable()
export class AwsCognitoAuthService {
    private readonly userPool: CognitoUserPool;
    private readonly cognitoIdentityServiceProvider: CognitoUserSession
    private readonly providerClient: CognitoIdentityProviderClient;
    constructor(private readonly authConfig: AwsCognitoAuthConfig) {
        this.userPool = new CognitoUserPool({
            UserPoolId: this.authConfig.userPoolId,
            ClientId: this.authConfig.clientId,
        });

        this.providerClient = new CognitoIdentityProviderClient({
            region: this.authConfig.region
        });
    }

    // Add methods to manage user authentication, registration, and authorization here.
}

In the code above, we have created an AwsCognitoAuthService service.

Register User

Now that we have our service skeleton ready, its time to add method to register user:

To register a new user we want the user to use their email to as login & we also need to verify the email address.

Add User

First we will add a user, cognito will send a mail to users with the verification code (this is as per settings in cognito).

    registerUser(registerRequest: RegisterRequest): Promise<ISignUpResult> {
        const { email, password } = registerRequest;
        return new Promise((resolve, reject) => {
            return this.userPool.signUp(
                email,
                password,
                [new CognitoUserAttribute({ Name: 'email', Value: email })],
                null,
                (err, result) => {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(result);
                    }
                },
            );
        });
    }

The registerUser function is responsible for registering a user with the Cognito User Pool. It receives a RegisterRequest object containing the email and password of the user.

To accomplish this task, the function utilizes the userPool object, which is created during the initialization of the constructor. It then invokes the signUp method by passing in the email and password provided, along with a new instance of the CognitoUserAttribute object. The attribute object specifies the email attribute to be passed to the Cognito service during registration.

Upon successful registration, the signUp method returns a promise that resolves with an ISignUpResult object, containing information about the newly registered user. If the registration fails, the promise is rejected with an error object.

The function uses a new Promise object to wrap the signUp method, allowing for convenient error handling using the reject and resolve methods. If the signUp method resolves successfully, the resolve method is called with the ISignUpResult object. In the case of an error, the reject method is called with the error object.

Verify user using email

The verifyUser function is responsible for verifying the registration of a user with the provided verification code. It takes in two parameters: the user’s email and the verification code.

    verifyUser(email, verificationCode) {
        return new Promise((resolve, reject) => {
            return new CognitoUser({ Username: email, Pool: this.userPool})
                .confirmRegistration(verificationCode,
                    true,
                    (err, result) => {
                        if(err) {
                            reject(err);
                        } else {
                            resolve(result);
                        }
                })
        });
    }

To handle the asynchronous nature of the confirmation process, the function returns a new Promise object that resolves with a successful confirmation or rejects with an error.

The function creates a new instance of the CognitoUser object using the provided email and the userPool object created during the constructor initialization. It then calls the confirmRegistration method of the CognitoUser object, passing in the verification code and a boolean flag to force alias creation if necessary. The callback function takes two parameters, err and result.

If there is an error during the confirmation process, the promise is rejected with the err object. If the confirmation process is successful, the promise is resolved with the result object.

Authenticate User

Create a function that will be used to authenticate a user with the Cognito User Pool. It takes a LoginRequest object as input, which contains the user’s email and password.

    authenticateUser(loginRequest: LoginRequest) {
        const { email, password } = loginRequest;

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

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

        const newUser = new CognitoUser(userData);

        return new Promise<CognitoUserSession>((resolve, reject) => {
            return newUser.authenticateUser(authenticationDetails, {
                onSuccess: result => {
                    resolve(result);
                },
                onFailure: err => {
                    reject(err);
                },
            });
        });
    }

The function creates an instance of the AuthenticationDetails class, which is used to store the user’s email and password. It also creates an object userData with the user’s email and a reference to the userPool object.

The function then creates a new instance of the CognitoUser class using the userData object.

The function returns a Promise object that resolves with a CognitoUserSession object if the authentication is successful, or rejects with an error if the authentication fails. The newUser object’s authenticateUser method is called with the authenticationDetails object and an object containing two properties, onSuccess and onFailure.

If the authentication is successful, the onSuccess method is called with the CognitoUserSession object, which is then resolved with the Promise object. If the authentication fails, the onFailure method is called with an error object, which is then rejected with the Promise object.

Forgot Password (reset via email)

When user forgets his password we need to provide a mechanism to reset the password. This will be done in two steps

  • A verification code will be sent to email.
  • Set the new password with the verification code sent on the email.

Send forgot password email

The forgotPassword function is used to initiate the forgotten password flow for a user in a Cognito User Pool. The function takes an email as input and creates a new CognitoUser object with the Username set to the email and a reference to the userPool object.

    forgotPassword(email) {
        return new Promise((resolve, reject) => {
            return new CognitoUser({Username: email, Pool: this.userPool}).forgotPassword(
                {
                    onSuccess: function (result) {
                        resolve(result);
                    },
                    onFailure: function (err) {
                        reject(err)
                    }
                });
        });
    }

Set new password

The setNewPassword function is used to set a new password for a user who has forgotten their password and gone through the password reset process. It takes three parameters: the user’s email, the verification code sent to the user to confirm their identity, and the new password they want to set.

    confirmPassword(email, verificationCode, newPassword) {
        return new Promise((resolve, reject) => {
            return new CognitoUser({Username: email, Pool: this.userPool}).confirmPassword(verificationCode, newPassword, {
                onSuccess: function (result) {
                    resolve(result);
                },
                onFailure: function (err) {
                    reject(err)
                }
            });
        });
    }

Inside the function, a new Promise object is created to handle the asynchronous nature of the password reset process. The function creates a new instance of the CognitoUser class using the user’s email and the userPool object.

The confirmPassword method of the CognitoUser object is then called with the verificationCode and newPassword parameters, along with an object containing two properties, onSuccess and onFailure.

If the password reset is successful, the onSuccess method is called with the result object, which is then resolved with the Promise object. If there is an error during the password reset process, the onFailure method is called with the error object, which is then rejected with the Promise object.

Delete user

The deleteUser function is used to delete a user from the Amazon Cognito User Pool. It takes a single parameter sub which is the user’s sub (subject) attribute.

    async deleteUser(sub: string) {
        const adminDeleteUserCommand = new AdminDeleteUserCommand({
            Username: sub,
            UserPoolId: this.authConfig.userPoolId
        });

        await this.providerClient.send(adminDeleteUserCommand)
    }

Inside the function, a new instance of the AdminDeleteUserCommand class is created with an object that contains the Username of the user to be deleted and the UserPoolId of the user pool where the user is registered.

The await keyword is used to wait for the send method of the providerClient object to send the adminDeleteUserCommand to the Cognito service to delete the user.

If successful, the function completes without returning anything. If there is an error, it will throw an exception that can be caught and handled by calling code.

Conclusion

In this article, we have reviewed various code snippets related to user authentication and management in AWS Cognito. We have discussed the purpose and functionality of functions such as authenticateUser, forgotPassword, confirmPassword, setNewPassword, and deleteUser. Overall, these functions provide a solid foundation for managing user authentication and access control in AWS Cognito.

If you liked this article, you can buy me a coffee

Categories:

Updated:

Kumar Rohit
WRITTEN BY

Kumar Rohit

I like long drives, bike trip & good food. I have passion for coding, especially for Clean-Code.

Leave a comment