Skip to content

Commit faf6693

Browse files
feat(cognito): user pool verification and invitation messages (#6282)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent e307d7f commit faf6693

File tree

6 files changed

+494
-10
lines changed

6 files changed

+494
-10
lines changed

packages/@aws-cdk/aws-cognito/README.md

+93
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,97 @@
1515
---
1616
<!--END STABILITY BANNER-->
1717

18+
[Amazon Cognito](https://docs.aws.amazon.com/cognito/latest/developerguide/what-is-amazon-cognito.html) provides
19+
authentication, authorization, and user management for your web and mobile apps. Your users can sign in directly with a
20+
user name and password, or through a third party such as Facebook, Amazon, Google or Apple.
21+
22+
The two main components of Amazon Cognito are [user
23+
pools](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html) and [identity
24+
pools](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-identity.html). User pools are user directories
25+
that provide sign-up and sign-in options for your app users. Identity pools enable you to grant your users access to
26+
other AWS services.
27+
1828
This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project.
29+
30+
## User Pools
31+
32+
User pools allow creating and managing your own directory of users that can sign up and sign in. They enable easy
33+
integration with social identity providers such as Facebook, Google, Amazon, Microsoft Active Directory, etc. through
34+
SAML.
35+
36+
Using the CDK, a new user pool can be created as part of the stack using the construct's constructor. You may specify
37+
the `userPoolName` to give your own identifier to the user pool. If not, CloudFormation will generate a name.
38+
39+
```ts
40+
new UserPool(this, 'myuserpool', {
41+
userPoolName: 'myawesomeapp-userpool',
42+
});
43+
```
44+
45+
### Sign Up
46+
47+
Users can either be signed up by the app's administrators or can sign themselves up. Once a user has signed up, their
48+
account needs to be confirmed. Cognito provides several ways to sign users up and confirm their accounts. Learn more
49+
about [user sign up here](https://docs.aws.amazon.com/cognito/latest/developerguide/signing-up-users-in-your-app.html).
50+
51+
When a user signs up, email and SMS messages are used to verify their account and contact methods. The following code
52+
snippet configures a user pool with properties relevant to these verification messages -
53+
54+
```ts
55+
new UserPool(this, 'myuserpool', {
56+
// ...
57+
selfSignUpEnabled: true,
58+
userVerification: {
59+
emailSubject: 'Verify your email for our awesome app!',
60+
emailBody: 'Hello {username}, Thanks for signing up to our awesome app! Your verification code is {####}',
61+
emailStyle: VerificationEmailStyle.CODE,
62+
smsMessage: 'Hello {username}, Thanks for signing up to our awesome app! Your verification code is {####}',
63+
}
64+
});
65+
```
66+
67+
By default, self sign up is disabled. Learn more about [email and SMS verification messages
68+
here](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-message-customizations.html).
69+
70+
Besides users signing themselves up, an administrator of any user pool can sign users up. The user then receives an
71+
invitation to join the user pool. The following code snippet configures a user pool with properties relevant to the
72+
invitation messages -
73+
74+
```ts
75+
new UserPool(this, 'myuserpool', {
76+
// ...
77+
userInvitation: {
78+
emailSubject: 'Invite to join our awesome app!',
79+
emailBody: 'Hello {username}, you have been invited to join our awesome app! Your temporary password is {####}',
80+
smsMessage: 'Your temporary password for our awesome app is {####}'
81+
}
82+
});
83+
```
84+
85+
All email subjects, bodies and SMS messages for both invitation and verification support Cognito's message templating.
86+
Learn more about [message templates
87+
here](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-message-templates.html).
88+
89+
### Security
90+
91+
Cognito sends various messages to its users via SMS, for different actions, ranging from account verification to
92+
marketing. In order to send SMS messages, Cognito needs an IAM role that it can assume, with permissions that allow it
93+
to send SMS messages. By default, CDK will create this IAM role but can also be explicily specified to an existing IAM
94+
role using the `smsRole` property.
95+
96+
```ts
97+
import { Role } from '@aws-cdk/aws-iam';
98+
99+
const poolSmsRole = new Role(this, 'userpoolsmsrole', { /* ... */ });
100+
101+
new UserPool(this, 'myuserpool', {
102+
// ...
103+
smsRole: poolSmsRole,
104+
smsRoleExternalId: 'c87467be-4f34-11ea-b77f-2e728ce88125'
105+
});
106+
```
107+
108+
When the `smsRole` property is specified, the `smsRoleExternalId` may also be specified. The value of
109+
`smsRoleExternalId` will be used as the `sts:ExternalId` when the Cognito service assumes the role. In turn, the role's
110+
assume role policy should be configured to accept this value as the ExternalId. Learn more about [ExternalId
111+
here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html).

packages/@aws-cdk/aws-cognito/lib/user-pool.ts

+181-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as iam from '@aws-cdk/aws-iam';
1+
import { IRole, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam';
22
import * as lambda from '@aws-cdk/aws-lambda';
33
import { Construct, IResource, Lazy, Resource } from '@aws-cdk/core';
44
import { CfnUserPool } from './cognito.generated';
@@ -212,6 +212,78 @@ export interface UserPoolTriggers {
212212
[trigger: string]: lambda.IFunction | undefined;
213213
}
214214

215+
/**
216+
* The email verification style
217+
*/
218+
export enum VerificationEmailStyle {
219+
/** Verify email via code */
220+
CODE = 'CONFIRM_WITH_CODE',
221+
/** Verify email via link */
222+
LINK = 'CONFIRM_WITH_LINK',
223+
}
224+
225+
/**
226+
* User pool configuration for user self sign up.
227+
*/
228+
export interface UserVerificationConfig {
229+
/**
230+
* The email subject template for the verification email sent to the user upon sign up.
231+
* See https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-message-templates.html to
232+
* learn more about message templates.
233+
* @default 'Verify your new account'
234+
*/
235+
readonly emailSubject?: string;
236+
237+
/**
238+
* The email body template for the verification email sent to the user upon sign up.
239+
* See https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-message-templates.html to
240+
* learn more about message templates.
241+
* @default 'Hello {username}, Your verification code is {####}'
242+
*/
243+
readonly emailBody?: string;
244+
245+
/**
246+
* Emails can be verified either using a code or a link.
247+
* Learn more at https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-email-verification-message-customization.html
248+
* @default VerificationEmailStyle.CODE
249+
*/
250+
readonly emailStyle?: VerificationEmailStyle;
251+
252+
/**
253+
* The message template for the verification SMS sent to the user upon sign up.
254+
* See https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-message-templates.html to
255+
* learn more about message templates.
256+
* @default 'The verification code to your new account is {####}'
257+
*/
258+
readonly smsMessage?: string;
259+
}
260+
261+
/**
262+
* User pool configuration when administrators sign users up.
263+
*/
264+
export interface UserInvitationConfig {
265+
/**
266+
* The template to the email subject that is sent to the user when an administrator signs them up to the user pool.
267+
* @default 'Your temporary password'
268+
*/
269+
readonly emailSubject?: string;
270+
271+
/**
272+
* The template to the email body that is sent to the user when an administrator signs them up to the user pool.
273+
* @default 'Your username is {username} and temporary password is {####}.'
274+
*/
275+
readonly emailBody?: string;
276+
277+
/**
278+
* The template to the SMS message that is sent to the user when an administrator signs them up to the user pool.
279+
* @default 'Your username is {username} and temporary password is {####}'
280+
*/
281+
readonly smsMessage?: string;
282+
}
283+
284+
/**
285+
* Props for the UserPool construct
286+
*/
215287
export interface UserPoolProps {
216288
/**
217289
* Name of the user pool
@@ -220,6 +292,40 @@ export interface UserPoolProps {
220292
*/
221293
readonly userPoolName?: string;
222294

295+
/**
296+
* Whether self sign up should be enabled. This can be further configured via the `selfSignUp` property.
297+
* @default false
298+
*/
299+
readonly selfSignUpEnabled?: boolean;
300+
301+
/**
302+
* Configuration around users signing themselves up to the user pool.
303+
* Enable or disable self sign-up via the `selfSignUpEnabled` property.
304+
* @default - see defaults in UserVerificationConfig
305+
*/
306+
readonly userVerification?: UserVerificationConfig;
307+
308+
/**
309+
* Configuration around admins signing up users into a user pool.
310+
* @default - see defaults in UserInvitationConfig
311+
*/
312+
readonly userInvitation?: UserInvitationConfig;
313+
314+
/**
315+
* The IAM role that Cognito will assume while sending SMS messages.
316+
* @default - a new IAM role is created
317+
*/
318+
readonly smsRole?: IRole;
319+
320+
/**
321+
* The 'ExternalId' that Cognito service must using when assuming the `smsRole`, if the role is restricted with an 'sts:ExternalId' conditional.
322+
* Learn more about ExternalId here - https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html
323+
*
324+
* This property will be ignored if `smsRole` is not specified.
325+
* @default - No external id will be configured
326+
*/
327+
readonly smsRoleExternalId?: string;
328+
223329
/**
224330
* Method used for user registration & sign in.
225331
* Allows either username with aliases OR sign in with email, phone, or both.
@@ -400,12 +506,47 @@ export class UserPool extends Resource implements IUserPool {
400506
}
401507
}
402508

509+
const emailVerificationSubject = props.userVerification?.emailSubject ?? 'Verify your new account';
510+
const emailVerificationMessage = props.userVerification?.emailBody ?? 'Hello {username}, Your verification code is {####}';
511+
const smsVerificationMessage = props.userVerification?.smsMessage ?? 'The verification code to your new account is {####}';
512+
513+
const defaultEmailOption = props.userVerification?.emailStyle ?? VerificationEmailStyle.CODE;
514+
const verificationMessageTemplate: CfnUserPool.VerificationMessageTemplateProperty =
515+
(defaultEmailOption === VerificationEmailStyle.CODE) ? {
516+
defaultEmailOption,
517+
emailMessage: emailVerificationMessage,
518+
emailSubject: emailVerificationSubject,
519+
smsMessage: smsVerificationMessage,
520+
} : {
521+
defaultEmailOption,
522+
emailMessageByLink: emailVerificationMessage,
523+
emailSubjectByLink: emailVerificationSubject,
524+
smsMessage: smsVerificationMessage
525+
};
526+
527+
const inviteMessageTemplate: CfnUserPool.InviteMessageTemplateProperty = {
528+
emailMessage: props.userInvitation?.emailBody,
529+
emailSubject: props.userInvitation?.emailSubject,
530+
smsMessage: props.userInvitation?.smsMessage,
531+
};
532+
const selfSignUpEnabled = props.selfSignUpEnabled !== undefined ? props.selfSignUpEnabled : false;
533+
const adminCreateUserConfig: CfnUserPool.AdminCreateUserConfigProperty = {
534+
allowAdminCreateUserOnly: !selfSignUpEnabled,
535+
inviteMessageTemplate: props.userInvitation !== undefined ? inviteMessageTemplate : undefined,
536+
};
537+
403538
const userPool = new CfnUserPool(this, 'Resource', {
404539
userPoolName: props.userPoolName,
405540
usernameAttributes,
406541
aliasAttributes,
407542
autoVerifiedAttributes: props.autoVerifiedAttributes,
408-
lambdaConfig: Lazy.anyValue({ produce: () => this.triggers })
543+
lambdaConfig: Lazy.anyValue({ produce: () => this.triggers }),
544+
smsConfiguration: this.smsConfiguration(props),
545+
adminCreateUserConfig,
546+
emailVerificationMessage,
547+
emailVerificationSubject,
548+
smsVerificationMessage,
549+
verificationMessageTemplate,
409550
});
410551

411552
this.userPoolId = userPool.ref;
@@ -528,8 +669,45 @@ export class UserPool extends Resource implements IUserPool {
528669
private addLambdaPermission(fn: lambda.IFunction, name: string): void {
529670
const normalize = name.charAt(0).toUpperCase() + name.slice(1);
530671
fn.addPermission(`${normalize}Cognito`, {
531-
principal: new iam.ServicePrincipal('cognito-idp.amazonaws.com'),
672+
principal: new ServicePrincipal('cognito-idp.amazonaws.com'),
532673
sourceArn: this.userPoolArn
533674
});
534675
}
676+
677+
private smsConfiguration(props: UserPoolProps): CfnUserPool.SmsConfigurationProperty {
678+
if (props.smsRole) {
679+
return {
680+
snsCallerArn: props.smsRole.roleArn,
681+
externalId: props.smsRoleExternalId
682+
};
683+
} else {
684+
const smsRoleExternalId = this.node.uniqueId.substr(0, 1223); // sts:ExternalId max length of 1224
685+
const smsRole = props.smsRole ?? new Role(this, 'smsRole', {
686+
assumedBy: new ServicePrincipal('cognito-idp.amazonaws.com', {
687+
conditions: {
688+
StringEquals: { 'sts:ExternalId': smsRoleExternalId }
689+
}
690+
}),
691+
inlinePolicies: {
692+
/*
693+
* The UserPool is very particular that it must contain an 'sns:Publish' action as an inline policy.
694+
* Ideally, a conditional that restricts this action to 'sms' protocol needs to be attached, but the UserPool deployment fails validation.
695+
* Seems like a case of being excessively strict.
696+
*/
697+
'sns-publish': new PolicyDocument({
698+
statements: [
699+
new PolicyStatement({
700+
actions: [ 'sns:Publish' ],
701+
resources: [ '*' ],
702+
})
703+
]
704+
})
705+
}
706+
});
707+
return {
708+
externalId: smsRoleExternalId,
709+
snsCallerArn: smsRole.roleArn
710+
};
711+
}
712+
}
535713
}

packages/@aws-cdk/aws-cognito/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"@aws-cdk/assert": "1.25.0",
6666
"@types/nodeunit": "^0.0.30",
6767
"cdk-build-tools": "1.25.0",
68+
"cdk-integ-tools": "1.25.0",
6869
"cfn2ts": "1.25.0",
6970
"jest": "^24.9.0",
7071
"nodeunit": "^0.11.3",
@@ -104,10 +105,9 @@
104105
"docs-public-apis:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientClientSecret",
105106
"docs-public-apis:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientId",
106107
"docs-public-apis:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientName",
107-
"docs-public-apis:@aws-cdk/aws-cognito.UserPoolProps",
108108
"docs-public-apis:@aws-cdk/aws-cognito.UserPoolAttributes",
109109
"docs-public-apis:@aws-cdk/aws-cognito.UserPoolClientProps"
110110
]
111111
},
112112
"stability": "experimental"
113-
}
113+
}

0 commit comments

Comments
 (0)