The AWS CDK Guide to Create your own Amazon Cognito User Pool and Client
Andrew Pettigrew
Software Engineer, Backend | Building scalable, high-performance systems with AWS, Java, JavaScript, ReactJS and PHP
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.
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
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.
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:
// 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.
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.