The AWS CDK Guide to Create your own Amazon Cognito User Pool and Client

The AWS CDK Guide to Create your own Amazon Cognito User Pool and Client

Amazon Cognito User Pool is a user directory for web and mobile app authentication and authorization. A user pool provides additional features for security, identity federation, and customization of the user experience. With AWS Cognito User Pool you can do the following with your users: sign up, sign in, provide a prebuilt hosted UI, forget password flow, sign up confirmation flow and more. We saw how we could create a User Pool via the console. We saw all the properties we could update while going through the wizard. However, we realize how time intensive this was and we are now looking for a faster way to create our user pool without manually going through the steps. In this guide we will be creating our own user pool using AWS Cloud Development Kit (AWS CDK) which is an infrastructure-as-code (IaC) tool that allows to automate our AWS infrastructure and resources. This will make it easier and faster to apply changes to our user pool as developers.

If you have not visited the previous guide on the topic, feel free to review the guide below and meet me back here.

https://www.dhirubhai.net/pulse/complete-guide-implement-aws-cognito-using-amplify-nextjs-pettigrew-e1xic/

What is AWS CDK

AWS Cloud Development Kit (AWS CDK) is an open-source software development framework for defining cloud infrastructure in code and provisioning it through AWS CloudFormation. In concept it’s no different to other IaC tool such as Terraform, Serverless Framework, Ansible, you name it however this tool is native to AWS only and provides the ability to create IaC using different supported programming languages. We will quickly review the main concepts on AWS CDK.

The AWS CDK consists of two primary parts:

  • AWS CDK Construct Library
  • AWS CDK Command Line Interface (AWS CDKCLI)

AWS CDK Construct Library

A collection of pre-written modular and reusable pieces of code, called constructs, that you can use, modify, and integrate to develop your infrastructure quickly. Simply put, this allows us to create different AWS resources like S3 buckets and lambda functions.

AWS CDK Command Line Interface (AWS CDKCLI)

A command line tool for interacting with CDK apps.

Before we go any further let's ensure we are on the same page when it comes to a few terminology we will encounter.

CloudFormation

AWS CloudFormation is a service that helps you model and set up your AWS resources so that you can spend less time managing those resources. AWD CDK allows us to create scripts that in end generates AWS Cloud Formation templates. CDK just allows us to define our templates via code rather than YAML or JSON.

Stack

When you use CloudFormation/AWS CDK, you manage related resources as a single unit called a stack. You create, update, and delete a collection of resources by creating, updating, and deleting stacks.

How to setup AWS CDK

Before we begin, If you don’t yet have an amazon account, you can easily find a guide online to help you set it up then meet me back here. Also ensure to setup the AWS Credential CLI as well.

Here we will go over how to initialize our AWS CDK App. To get started with CDK we first need to install it.

npm i -g aws-cdk        

We can check if the installation was successful with the following command

cdk --version        

We will initialize (i.e create) the CDK project. For this guide we will be using Typescript. Create a folder specific to this project and run this command inside it.

cdk init --language=typescript        

Once successful you should see a project with the following folder structure.

Image of a project folder structure
CDK project structure

Let’s quickly go over what the folders are for:

bin - Contains the code to initialize the CDK project.

lib - Contains our actual stacks i.e AWS resources.

node_modules - Contains our project dependencies.

test - Contains test file to test our stacks setup. The concept is no different from test in frontend and backend applications.


Create AWS CDK Code for Cognito User Pool Stack

Let’s now create our User Pool stack in the lib folder. Create file called auth-stack.ts. We will build our user pool stack step by step similar to how we did in the console. Here we are creating an AuthStack that extends cdk.Stack. All of our resources will be created inside the constructor but we will create two methods to organize how we create the user pool and user pool client.

import * as cdk from 'aws-cdk-lib';
import { UserPool, UserPoolClient } from 'aws-cdk-lib/aws-cognito';
import { Construct } from 'constructs';

export class AuthStack extends cdk.Stack {
  public userPool: UserPool;
  private userPoolClient: UserPoolClient;

  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    this.createUserPool();
    this.createUserPoolClient();
  }

  private createUserPool() {
  // code goes here
  }

  private createUserPoolClient() {
   // code goes here
  }

}        

Let’s now first create a user pool construct.

 private createUserPool() {
    this.userPool = new UserPool(this, 'MySuperAwesomeUserPool', {});
 }        

Configure User Pool Sign-In options.

private createUserPool() {
    this.userPool = new UserPool(this, 'MySuperAwesomeUserPool', {
      // Methods in which a user registers or signs in to a user pool
      signInAliases: {
        email: true
      },
    });
  }        

Configure Password Policy.

private createUserPool() {

    this.userPool = new UserPool(this, 'MySuperAwesomeUserPool', {
      // Methods in which a user registers or signs in to a user pool
      signInAliases: {
        email: true
      },

      // Password policy for this user pool.
      passwordPolicy: {
        minLength: 8,
        requireLowercase: true,
        requireUppercase: true,
        requireDigits: true,
        requireSymbols: true,
      },
    });
  }        

Configure MFA Authentication Settings. We have opted to turn this off for now.

private createUserPool() {

    this.userPool = new UserPool(this, 'MySuperAwesomeUserPool', {
      // Methods in which a user registers or signs in to a user pool
      signInAliases: {
        email: true
      },

      // Password policy for this user pool.
      passwordPolicy: {
        minLength: 8,
        requireLowercase: true,
        requireUppercase: true,
        requireDigits: true,
        requireSymbols: true,
      },
       // The different ways in which a user pool's MFA enforcement can be configured. Off by default.
       mfa: Mfa.OFF,
    });
  }        

Configure User Account Recover options.

private createUserPool() {

    this.userPool = new UserPool(this, 'MySuperAwesomeUserPool', {
      // Methods in which a user registers or signs in to a user pool
      signInAliases: {
        email: true
      },

      // Password policy for this user pool.
      passwordPolicy: {
        minLength: 8,
        requireLowercase: true,
        requireUppercase: true,
        requireDigits: true,
        requireSymbols: true,
      },
      // The different ways in which a user pool's MFA enforcement can be configured. Off by default.
      mfa: Mfa.OFF,

      // Account recovery settings
      accountRecovery: AccountRecovery.EMAIL_ONLY,
    });
  }        

Configure Self Sign Up options.

private createUserPool() {

    this.userPool = new UserPool(this, 'MySuperAwesomeUserPool', {
      // Methods in which a user registers or signs in to a user pool
      signInAliases: {
        email: true
      },

      // Password policy for this user pool.
      passwordPolicy: {
        minLength: 8,
        requireLowercase: true,
        requireUppercase: true,
        requireDigits: true,
        requireSymbols: true,
      },
      // The different ways in which a user pool's MFA enforcement can be configured. Off by default.
      mfa: Mfa.OFF,

      // Account recovery settings
      accountRecovery: AccountRecovery.EMAIL_ONLY,

      // Whether self sign-up should be enabled
      selfSignUpEnabled: true,
    });
  }        

Configure Attribute verification and user account confirmation.

private createUserPool() {
    this.userPool = new UserPool(this, 'MySuperAwesomeUserPool', {
      // Methods in which a user registers or signs in to a user pool
      signInAliases: {
        email: true
      },

      // Password policy for this user pool.
      passwordPolicy: {
        minLength: 8,
        requireLowercase: true,
        requireUppercase: true,
        requireDigits: true,
        requireSymbols: true,
      },
      // The different ways in which a user pool's MFA enforcement can be configured. Off by default.
      mfa: Mfa.OFF,

      // Account recovery settings
      accountRecovery: AccountRecovery.EMAIL_ONLY,

      // Whether self sign-up should be enabled
      selfSignUpEnabled: true,

      // Cognito-assisted verification and confirmation
      userVerification: {
        emailSubject: 'Verify your email for our super awesome app!',
        emailBody: 'Hello {username}, Thanks for signing up to our super awesome app! Your verification code is {####}',
        emailStyle: VerificationEmailStyle.CODE,
        smsMessage: 'Hello {username}, Thanks for signing up to our super awesome app! Your verification code is {####}',
      },
    });
  }        

Configure Required Attributes.

private createUserPool() {
    this.userPool = new UserPool(this, 'MySuperAwesomeUserPool', {
      // Methods in which a user registers or signs in to a user pool
      signInAliases: {
        email: true
      },

      // Password policy for this user pool.
      passwordPolicy: {
        minLength: 8,
        requireLowercase: true,
        requireUppercase: true,
        requireDigits: true,
        requireSymbols: true,
      },
      // The different ways in which a user pool's MFA enforcement can be configured. Off by default.
      mfa: Mfa.OFF,

      // Account recovery settings
      accountRecovery: AccountRecovery.EMAIL_ONLY,

      // Whether self sign-up should be enabled
      selfSignUpEnabled: true,

      // Cognito-assisted verification and confirmation
      userVerification: {
        emailSubject: 'Verify your email for our super awesome app!',
        emailBody: 'Hello {username}, Thanks for signing up to our super awesome app! Your verification code is {####}',
        emailStyle: VerificationEmailStyle.CODE,
        smsMessage: 'Hello {username}, Thanks for signing up to our super awesome app! Your verification code is {####}',
      },
      standardAttributes: {
        fullname: {
          required: true,
          mutable: true,
        },
        email: {
          required: true,
          mutable: false,
        },
        givenName: {
          required: true,
          mutable: true,
        },
        familyName: {
          required: true,
          mutable: true,
        },
      },
    });
  }        

Configure Email.

private createUserPool() {
    this.userPool = new UserPool(this, 'MySuperAwesomeUserPool', {
      // Methods in which a user registers or signs in to a user pool
      signInAliases: {
        email: true
      },

      // Password policy for this user pool.
      passwordPolicy: {
        minLength: 8,
        requireLowercase: true,
        requireUppercase: true,
        requireDigits: true,
        requireSymbols: true,
      },
      // The different ways in which a user pool's MFA enforcement can be configured. Off by default.
      mfa: Mfa.OFF,

      // Account recovery settings
      accountRecovery: AccountRecovery.EMAIL_ONLY,

      // Whether self sign-up should be enabled
      selfSignUpEnabled: true,

      // Cognito-assisted verification and confirmation
      userVerification: {
        emailSubject: 'Verify your email for our super awesome app!',
        emailBody: 'Hello {username}, Thanks for signing up to our super awesome app! Your verification code is {####}',
        emailStyle: VerificationEmailStyle.CODE,
        smsMessage: 'Hello {username}, Thanks for signing up to our super awesome app! Your verification code is {####}',
      },
      standardAttributes: {
        fullname: {
          required: true,
          mutable: true,
        },
        email: {
          required: true,
          mutable: false,
        },
        givenName: {
          required: true,
          mutable: true,
        },
        familyName: {
          required: true,
          mutable: true,
        },
      },
      // Configure how your user pool sends email messages to users.
      email: UserPoolEmail.withCognito(),
    });
  }        

Configure User Pool Name.

private createUserPool() {
    this.userPool = new UserPool(this, 'MySuperAwesomeUserPool', {
      // Methods in which a user registers or signs in to a user pool
      signInAliases: {
        email: true
      },

      // Password policy for this user pool.
      passwordPolicy: {
        minLength: 8,
        requireLowercase: true,
        requireUppercase: true,
        requireDigits: true,
        requireSymbols: true,
      },
      // The different ways in which a user pool's MFA enforcement can be configured. Off by default.
      mfa: Mfa.OFF,

      // Account recovery settings
      accountRecovery: AccountRecovery.EMAIL_ONLY,

      // Whether self sign-up should be enabled
      selfSignUpEnabled: true,

      // Cognito-assisted verification and confirmation
      userVerification: {
        emailSubject: 'Verify your email for our super awesome app!',
        emailBody: 'Hello {username}, Thanks for signing up to our super awesome app! Your verification code is {####}',
        emailStyle: VerificationEmailStyle.CODE,
        smsMessage: 'Hello {username}, Thanks for signing up to our super awesome app! Your verification code is {####}',
      },
      standardAttributes: {
        fullname: {
          required: true,
          mutable: true,
        },
        email: {
          required: true,
          mutable: false,
        },
        givenName: {
          required: true,
          mutable: true,
        },
        familyName: {
          required: true,
          mutable: true,
        },
      },
      // Configure how your user pool sends email messages to users.
      email: UserPoolEmail.withCognito(),
      // Create a friendly name for your user pool.
      userPoolName: 'my-super-awesome-user-pool',
    });
  }        

Configure Hosted UI.

So far I couldn’t find any settings for customizing Hosted UI. I guess CDK isn’t complet in covering EVERYTHING in Cognito? At the time of this recording, there is even an open github issue where other devs express interest in being able to configure/customized hosted UI via CDK here https://github.com/aws/aws-cdk/issues/6953. There are workarounds to achieve this behaviour but “luckily” our user pool does not require Hosted UI so we can ignore this for now.

Please feel free to let me know when this has become outdated :) thank you. ??

Nice to haves

We can also configure a few nice to have such as adding:

deletionProtection - This will prevent you from accidentally deleting your user pool and in essence your users accounts. Bye bye startup.

removalPolicy - Policy to apply when the user pool is removed from the stack. There a few policy options to be aware of:

  • DESTROY: Physically destroys the resource when removed from the app.
  • RETAIN: Retains the resource in the account, orphaned from the stack.
  • SNAPSHOT: Deletes the resource but saves a snapshot of its data, allowing it to be re-created later. Available for stateful resources like databases and EC2 volumes.
  • RETAIN_ON_UPDATE_OR_DELETE: Retains resources when requested to be deleted or replaced during stack updates, except during creation rollbacks.

 // The removal policy to apply to this resource
removalPolicy: cdk.RemovalPolicy.DESTROY,

// Indicates whether the user pool should have deletion protection enabled
deletionProtection: true,
        

On completion your UserPool Stack should look like this:

private createUserPool() {
    this.userPool = new UserPool(this, 'MySuperAwesomeUserPool', {
      // Methods in which a user registers or signs in to a user pool
      signInAliases: {
        email: true
      },

      // Password policy for this user pool.
      passwordPolicy: {
        minLength: 8,
        requireLowercase: true,
        requireUppercase: true,
        requireDigits: true,
        requireSymbols: true,
      },
      // The different ways in which a user pool's MFA enforcement can be configured. Off by default.
      mfa: Mfa.OFF,

      // Account recovery settings
      accountRecovery: AccountRecovery.EMAIL_ONLY,

      // Whether self sign-up should be enabled
      selfSignUpEnabled: true,

      // Cognito-assisted verification and confirmation
      userVerification: {
        emailSubject: 'Verify your email for our super awesome app!',
        emailBody: 'Hello {username}, Thanks for signing up to our super awesome app! Your verification code is {####}',
        emailStyle: VerificationEmailStyle.CODE,
        smsMessage: 'Hello {username}, Thanks for signing up to our super awesome app! Your verification code is {####}',
      },
      standardAttributes: {
        fullname: {
          required: true,
          mutable: true,
        },
        email: {
          required: true,
          mutable: false,
        },
        givenName: {
          required: true,
          mutable: true,
        },
        familyName: {
          required: true,
          mutable: true,
        },
      },
      // Configure how your user pool sends email messages to users.
      email: UserPoolEmail.withCognito(),
      // Create a friendly name for your user pool.
      userPoolName: 'my-super-awesome-user-pool',
      // The removal policy to apply to this resource
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      // Indicates whether the user pool should have deletion protection enabled.
      deletionProtection: true,
    });
  }        

AWS CDK Code for User Pool Client

Let’s now first create a user pool client construct. Take note of the fact that we are adding a client to the already created this.userPool object.

private createUserPoolClient() {
    this.userPool.addClient('MySuperAwesomeUserPoolClient', {});
}        

Configure Client name.

private createUserPoolClient() {
    this.userPool.addClient('MySuperAwesomeUserPoolClient', {
      // Enter a friendly name for your user pool client.
      userPoolClientName: 'my-awesome-app',
    });
  }        

Configure Secret.

private createUserPoolClient() {
    this.userPool.addClient('MySuperAwesomeUserPoolClient', {
      // Enter a friendly name for your user pool client.
      userPoolClientName: 'my-awesome-app',
      // Whether to generate a client secret
      generateSecret: false,
    });
  }        

Configure Auth Flow.

private createUserPoolClient() {
    this.userPool.addClient('MySuperAwesomeUserPoolClient', {
      // Enter a friendly name for your user pool client.
      userPoolClientName: 'my-awesome-app',
      // Whether to generate a client secret
      generateSecret: false,
      authFlows: {
        userPassword: true,
        userSrp: true,
      }
    });
  }        

On completion your User Pool Client will look like this

private createUserPoolClient() {
    this.userPool.addClient('MySuperAwesomeUserPoolClient', {
      // Enter a friendly name for your user pool client.
      userPoolClientName: 'my-awesome-app',
      // Whether to generate a client secret
      generateSecret: false,
      authFlows: {
        userPassword: true,
        userSrp: true,
      }
    });
  }        

Let’s now ensure we can actually deploy our stack. We will do so by updating the file under the bin folder that should be added when you create your CDK app.


import * as cdk from 'aws-cdk-lib';
import 'source-map-support/register';
import { AuthStack } from '../lib/auth-stack';

const app = new cdk.App();
const authStack = new AuthStack(app, 'AuthStack', {});
        

Now that will are have our stack referenced we can begin to deploy our stack

cdk deploy        

Once successful your output should like this. Total time was 20.27s. How long did we take to create our User Pool via the console? ????


Create User with AWS CLI

Let’s now test this bad boy out. We will be using the CLI to create and sign in our user into our user pool. Note: You will need to retrieve your Client ID and User Pool ID. Not sure where to find them, consider reviewing this guide to learn how to.

https://www.dhirubhai.net/pulse/complete-guide-implement-aws-cognito-using-amplify-nextjs-pettigrew-e1xic/


Sign Up User.

aws cognito-idp sign-up \
    --client-id <app-client-id> \
    --username <email-address> \
    --password 'A1b@Cdef' \
    --user-attributes \
        Name="email",Value="<email address>" \
        Name="family_name",Value="Parker" \
        Name="given_name",Value="Parker" \
        Name="name",Value="Peter Park" \
    --region us-east-1        

Confirm Sign Up User.

aws cognito-idp admin-confirm-sign-up \
    --user-pool-id <user-pool-id> \
    --username <email-address> \
    --region us-east-1        

Login User.

aws cognito-idp initiate-auth --auth-flow USER_PASSWORD_AUTH \
    --client-id <app-client-id> \
    --auth-parameters USERNAME=<email-address>,PASSWORD='A1b@Cdef'
    --region us-east-1        

Once successful your output should like this. Which is proof our user was created and able to login.

{
    "ChallengeParameters": {},
    "AuthenticationResult": {
        "AccessToken": "eyJraWQiOiJVZXowcXJPe....",
        "ExpiresIn": 3600,
        "TokenType": "Bearer",
        "RefreshToken": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R...."
    }
}
        

Utilize CDK Output

We saw that we had to search via AWS Console to retrieve our Client ID and User Pool ID. It would be nice to get those values from the CDK itself and we can. To output the User Pool ID and Client ID for our stack, add the following lines at the end of your createUserPool() method and createUserPoolClient() method, respectively.

 new cdk.CfnOutput(this, 'MySuperAwesomeUserPoolId', {
      value: this.userPool.userPoolId
 });        
 new cdk.CfnOutput(this, 'MySuperAwesomeUserPoolClientId', {
      value: this.userPoolClient.userPoolClientId,
});        

Once completed redeploy your stack again. This time in the terminal output we should be able to see the output values we created in the stack.


Remove the stack when you’re done.

Destroy Stack

cdk destroy        

You may now encounter an error that prevents you from destroying the User Pool. Earlier we had set our deletionProtection policy to true. To delete your user pool we now have to first deactivate this policy then delete it again. We will deactivate the policy via the console. From your User Pool Console, Click User Pool Properties

Scroll until you have identified the Delete Protection section. Simply click the deactivate button and follow the steps. Once deactivated run the cdk destroy command again.




Conclusion

In this guide we saw how to automate our AWS Cognito User Pool and User Pool Client infrastructure using AWS CDK. There are plenty of benefits in utilizing an IaC tool such as AWS CDK to develop reliable, scalable, cost-effective applications in the cloud with the considerable expressive power of a programming language. With AWS CDK we are able to develop and manage your infrastructure as code (IaC), define your cloud infrastructure using general-purpose programming languages, deploy infrastructure through AWS CloudFormation, get started developing your application quickly with constructs.

Feedback is very important to me and it's great way to improve the quality of this guide. I welcome your thoughts to ensure this guide makes it easier for the next reader. Feel free to visit the comment section or smash that like button.

要查看或添加评论,请登录

Andrew Pettigrew的更多文章

  • TypeScript Advanced Type Inferences

    TypeScript Advanced Type Inferences

    TypeScript offers a rich set of tools for crafting custom types. We can define new types, build upon existing ones…

  • The Complete Guide to Implement AWS Cognito using Amplify with NextJS

    The Complete Guide to Implement AWS Cognito using Amplify with NextJS

    This guide will cover how to implement prebuilt authentication flows with AWS Cognito and Next.js 14 using your own…

    1 条评论
  • Getting Started with Cloud Computing

    Getting Started with Cloud Computing

    Do you need a quick understanding of the cloud, what it is, how it works, and what’s it made up of? This primer on…

  • Value Proposition Design

    Value Proposition Design

    Have you ever thought about why you buy from one company versus another? Think about two businesses that are doing the…

  • Evolution of The Web 1.0 - 3.0

    Evolution of The Web 1.0 - 3.0

    The web is the primary tool used by billions of people all over the world, who use it to browse, read, write and share…

    2 条评论
  • What is Product Management

    What is Product Management

    Originally posted here: What is product mangement? Who are product managers? What do they even do? Do you need them?…

  • How to be more productive with Trello + Google Calendar in 2021

    How to be more productive with Trello + Google Calendar in 2021

    Introduction Disclaimer: This might not work for you and as such, you may need to find an alternative solution that…

  • The Development of a NFC Mobile Application for JUTC

    The Development of a NFC Mobile Application for JUTC

    In light of my previous post Managing JUTC Public Wifi, I felt some youthful exuberance to go back in the archives of…

    2 条评论
  • Managing JUTC Public Wifi

    Managing JUTC Public Wifi

    Introduction In this post, I'll be going over a project my team and I under took this year, 2020. This project was…

    1 条评论
  • Intro to RESTful API

    Intro to RESTful API

    What is RESTful API REST is an acronym for Resist Eating Stale-food Today..

社区洞察

其他会员也浏览了