We will be creating a new Amazon Cognito Userpool and app client. We will look into different option available to us for configuring cognito authentication.
Amazon cognito userpool is a secure identity store that lets you add user sign-up, sign-in, and access control to aws resources from your web and mobile apps quickly and easily. An App client is an entity within userpool that lets you call unauthenticated api like signin, signup from your app, App client have an app id and optional secure secret so that only authorised client app can call these unauthorised apis.
We will be writing cdk code in typescript and will use CDK toolkit aws-cdk
to initialise a starter project. We will first create a new folder cdk-cognito-userpool
and cd
into the folder, the name of the folder will be used by cdk toolkit to name the stack but it can be changed later.
mkdir cdk-cognito-userpool
cd cdk-cognito-userpool
We can now run the following command to initialise project will be using the default app
template.
npx aws-cdk init app --language=typescript
Only dependency that we have to add is aws-cdk-lib
npm i aws-cdk-lib
We will define the stack inside lib > cdk-cognito-userpool-stack.ts
. Lets start by creating a userpool basic and we will add configuration option later on.
We will use the Userpool
construct from cognito to create userpool.
userPoolName
property can be used to define a custom name for cogninto userpool resource. If this property is not defined CloudFormation will automatically generate a name for the resource.selfSignUpEnabled
if this is set to false
then a user can only be signed up by admin.import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as cognito from "aws-cdk-lib/aws-cognito";
export class CdkCognitoUserpoolStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const deznitUserpool = new cognito.UserPool(this, "deznituserpool", {
/**
* you can specify userPoolName if not cloudformation will generate a name
*/
userPoolName: "deznit-userpool",
/**
* if false user can only be invided by admin
*/
selfSignUpEnabled: true,
});
}
}
userVerification
is used to customise the email sent to user on signup.userInvitation
is email sent to user when a new user is created by admin.emailStyle
have two options cognito.VerificationEmailStyle.CODE
which sends an otp, and cognito.VerificationEmailStyle.LINK
which will send a verification link in the email.emailBody
for styling. if you use CODE
as verification type the emailBody must contain {###}
as a placeholder to insert OTP by cognito. If you use LINK
then emailBody must contain {##Verify Your Email##}
as placeholder for the link.const deznitUserpool = new cognito.UserPool(this, "deznituserpool", {
// ... rest
/**
* customizing verification email
*/
userVerification: {
emailStyle: cognito.VerificationEmailStyle.CODE,
emailSubject: "Deznit email verification",
emailBody:
"Thanks for signing up to Deznit Your verification code is {####}",
},
/**
* customising admin invite
*/
userInvitation: {
emailSubject: "Invitation to join Deznit",
emailBody:
"Hello {username}, you have been invited to join Deznit. Your temporary password is {####}",
},
});
The maximum length for the message, including the verification code (if present), is 20,000 UTF-8 characters
There are 4 different methods (username
, email
, phone
, prefferedUsername
) to let users signin to your app. This cannot be changed once userpool is created
.
username
is immutable, user will have to set an username at the time of signup.prefferedUsername
this is mutable and only available if username option is set to true.If email address is selected as an alias, Amazon Cognito doesn't accept a user name that matches a valid email address format. Similarly, if you select phone number as an alias, Amazon Cognito doesn't accept a user name for that user pool that matches a valid phone number format.
autoVerify
to true for the aliases.signInCaseSensitive
to false, default value is true.passwordPolicy
property. temporaryPasswordValidityDays
is the validity to password generated when a user is invited to app, if user doesn’t signup in the timeframe admin will have to reset the password.accountRecovery
is how user will be able to recover their account if they forget their password. Default value is phone if available otherwise email. User wont be able to use phone for recovery if it is used for MFA.const deznitUserpool = new cognito.UserPool(this, "deznituserpool", {
// ...rest
signInAliases: { email: true },
/**
* cognito will request verification for following
*/
autoVerify: { email: true },
signInCaseSensitive: false, //default true
passwordPolicy: {
minLength: 6,
requireDigits: true,
requireLowercase: false,
requireUppercase: false,
requireSymbols: false,
temporaryPasswordValidityDays: 30,
},
/**
* This is how user will be able to recover their account
*/
accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
});
We can associate attributes with a cognito user. Cognito have some predefined attributes called standardAttributes
which when set as required
user wont be able to signup without providing that info. Apart from the standard attributes we can also set am maximum of 50 customAttributes
also.
const deznitUserpool = new cognito.UserPool(this, "deznituserpool", {
// ...rest
/**
* Standard attributes that cognito supports
* if required user wont be able to signup unless that attribute is provided
*/
standardAttributes: {
fullname: {
required: false,
mutable: true,
},
gender: {
required: false,
mutable: true,
},
},
/**
* custom attributes for a user
* max 50
*/
customAttributes: {
isAdmin: new cognito.BooleanAttribute({ mutable: true }),
level: new cognito.StringAttribute({ mutable: true }),
},
});
With this we have setup a basic cognito userPool, there are many other features like setting MFA, SES for sms and lambda triggers etc. which I will cover in future posts. Now below the userpool let’s create a Userpool App Client.
removalPolicy
specify if we want to DESTROY
or RETAIN
the userPool on running cdk destroy
. Default value is RETAIN
.
const deznitUserpool = new cognito.UserPool(this, "deznituserpool", {
// ...rest
/**
* Deletes the userpool on cdk destroy
* default is RETAIN
*/
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
An App client lets you call unauthenticated api like signin, signup from your app, App client have an app id and optional secure secret so that only authorised client app can call these unauthorised apis.
cognito.ClientAttributes()
can be used to grant read and write permissions to cognito attributes from client app.standardAttributes
and customAttributes
isAdmin
const deznitUserpool = new cognito.UserPool(this, "deznituserpool", {
// ...userpool configuration
});
/**
* userpool client permissions to write read attributes
*/
const clientWriteAttributes = new cognito.ClientAttributes()
.withStandardAttributes({ fullname: true, gender: true })
.withCustomAttributes("isAdmin", "level");
const clientReadAttributes = clientWriteAttributes
.withStandardAttributes({
fullname: true,
gender: true,
emailVerified: true,
preferredUsername: true,
})
.withCustomAttributes("isAdmin", "level");
/**
* Adding userpool client
*/
const userpoolClient = deznitUserpool.addClient("app-client", {
userPoolClientName: "deznit-app-client",
readAttributes: clientReadAttributes,
writeAttributes: clientWriteAttributes,
});
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as cognito from "aws-cdk-lib/aws-cognito";
export class CdkCognitoUserpoolStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const deznitUserpool = new cognito.UserPool(this, "deznituserpool", {
/**
* you can specify userPoolName if not cloudformation will generate a name
*/
userPoolName: "deznit-userpool",
/**
* if false user can only be invided by admin
*/
selfSignUpEnabled: true,
/**
* customizing verification email
*/
userVerification: {
emailStyle: cognito.VerificationEmailStyle.CODE,
emailSubject: "Deznit email verification",
emailBody:
"Thanks for signing up to Deznit Your verification code is {####}",
},
/**
* customising admin invite
*/
userInvitation: {
emailSubject: "Invitation to join Deznit",
emailBody:
"Hello {username}, you have been invited to join Deznit. Your temporary password is {####}",
},
/**
* ways by which users can signin
* 4 options --> username, email, phone, preferredUsername
*
*/
signInAliases: { email: true },
/**
* cognito will request verification for following
*/
autoVerify: { email: true },
signInCaseSensitive: false,
passwordPolicy: {
minLength: 6,
requireDigits: true,
requireLowercase: false,
requireUppercase: false,
requireSymbols: false,
},
/**
* Standard attributes that cognito supports
* if required user wont be able to signup unless that attribute is provided
*/
standardAttributes: {
fullname: {
required: false,
mutable: true,
},
gender: {
required: false,
mutable: true,
},
},
/**
* custom attributes for a user
* max 50
*/
customAttributes: {
isAdmin: new cognito.BooleanAttribute({ mutable: true }),
level: new cognito.StringAttribute({ mutable: true }),
},
accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
/**
* The email from which cognito sends email
* for higher volume use Amazon SES
*/
email: cognito.UserPoolEmail.withCognito("info@deznit.com"),
/**
* Deletes the userpool on cdk destroy
* default is RETAIN
*/
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
/**
* userpool client permissions to write read attributes
*/
const clientWriteAttributes = new cognito.ClientAttributes()
.withStandardAttributes({ fullname: true, gender: true })
.withCustomAttributes("isAdmin", "level");
const clientReadAttributes = clientWriteAttributes
.withStandardAttributes({
fullname: true,
gender: true,
emailVerified: true,
preferredUsername: true,
})
.withCustomAttributes("isAdmin", "level");
/**
* Adding userpool client
*/
const userpoolClient = deznitUserpool.addClient("app-client", {
userPoolClientName: "deznit-app-client",
readAttributes: clientReadAttributes,
writeAttributes: clientWriteAttributes,
});
/**
* print values to console
*/
new cdk.CfnOutput(this, "aws_user_pools_id", {
value: deznitUserpool.userPoolId,
});
new cdk.CfnOutput(this, "aws_user_pools_web_client_id", {
value: userpoolClient.userPoolClientId,
});
}
}
cdk synth
cdk deploy
cdk-exports.ts
file create after cdk deploy.aws cognito-idp admin-create-user \
--user-pool-id YOUR_USER_POOL_ID \
--username deznit.text@gmail.com
You will get response similar to this after successful creation of user in cognito and in aws console you will be able to see the newly created user, an email will be sent with temporary password to the user.
{
"User": {
"Username": "59be41de-2c0e-4e4f-adf4-945b5fd2a56e",
"Attributes": [
{
"Name": "sub",
"Value": "59be41de-2c0e-4e4f-adf4-945b5fd2a56e"
},
{
"Name": "email",
"Value": "deznit.text@gmail.com"
}
],
"UserCreateDate": "2022-10-08T11:39:22.261000+05:30",
"UserLastModifiedDate": "2022-10-08T11:39:22.261000+05:30",
"Enabled": true,
"UserStatus": "FORCE_CHANGE_PASSWORD"
}
}
cdk destroy