Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion packages/@aws-cdk/aws-cognito/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,53 @@ All email subjects, bodies and SMS messages for both invitation and verification
Learn more about [message templates
here](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-message-templates.html).

### Sign In

Users registering or signing in into your application can do so with multiple identifiers. There are 4 options
available:

* `USERNAME`: Allow signing in using the one time immutable user name that the user chose at the time of sign up.
* `EMAIL`: Allow signing in using the email address that is associated with the account.
* `PHONE_NUMBER`: Allow signing in using the phone number that is associated with the account.
* `PREFERRED_USERNAME`: Allow signing in with an alternate user name that the user can change at any time. However, this
is not available if the USERNAME option is not chosen.

The following code sets up a user pool so that the user can sign in with either their username or their email address -

```ts
new UserPool(this, 'myuserpool', {
// ...
// ...
signInAliases: [ SignInAlias.USERNAME, SignInAlias.EMAIL ],
Comment thread
nija-at marked this conversation as resolved.
Outdated
});
```

User pools can either be configured so `USERNAME` is primary sign in form, but also allows for the other three to be
used additionally; or it can be configured so that email and/or phone numbers are the only ways a user can register and
sign in. Read more about this
[here](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-aliases-settings).

To match with 'Option 1' in the above link, with a verified email, this property should be set to
`[ UsernameAlias.USERNAME, UsernameAlias.EMAIL ]`. To match with 'Option 2' in the above link with both a verified
email and phone number, this property should be set to `[ UsernameAlias.EMAIL, UsernameAlias.PHONE ]`.

Cognito recommends that email and phone number be automatically verified, if they are one of the sign in methods for
the user pool. Read more about that
[here](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-aliases).
The CDK does this by default, when email and/or phone number are specified as part of `signInAliases`. This can be
overridden by specifying the `autoVerifiedAttributes` property.

The following code snippet sets up only email as a sign in alias, but both email and phone number to be auto-verified.

```ts
new UserPool(this, 'myuserpool', {
// ...
// ...
signInAliases: [ SignInAlias.USERNAME, SignInAlias.EMAIL ],
autoVerifiedAttributes: [ AutoVerifiedAttrs.EMAIL, AutoVerifiedAttrs.PHONE ]
Comment thread
nija-at marked this conversation as resolved.
Outdated
});
```

### Security

Cognito sends various messages to its users via SMS, for different actions, ranging from account verification to
Expand All @@ -108,4 +155,24 @@ new UserPool(this, 'myuserpool', {
When the `smsRole` property is specified, the `smsRoleExternalId` may also be specified. The value of
`smsRoleExternalId` will be used as the `sts:ExternalId` when the Cognito service assumes the role. In turn, the role's
assume role policy should be configured to accept this value as the ExternalId. Learn more about [ExternalId
here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html).
here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html).

### Importing User Pools

Any user pool that has been created outside of this stack, can be imported into the CDK app. Importing a user pool
allows for it to be used in other parts of the CDK app that reference an `IUserPool`. However, imported user pools have
limited configurability. As a rule of thumb, none of the properties that is are part of the
[`AWS::Cognito::UserPool`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpool.html)
CloudFormation resource can be configured.

User pools can be imported either using their id via the `UserPool.fromUserPoolId()`, or by using their ARN, via the
`UserPool.fromUserPoolArn()` API.

```ts
const stack = new Stack(app, 'my-stack');

const awesomePool = UserPool.fromUserPoolId(stack, 'awesome-user-pool', 'us-east-1_oiuR12Abd');

const otherAwesomePool = UserPool.fromUserPoolArn(stack, 'other-awesome-user-pool',
'arn:aws:cognito-idp:eu-west-1:123456789012:userpool/us-east-1_mtRyYQ14D');
```
191 changes: 78 additions & 113 deletions packages/@aws-cdk/aws-cognito/lib/user-pool.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IRole, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import { Construct, IResource, Lazy, Resource } from '@aws-cdk/core';
import { Construct, IResource, Lazy, Resource, Stack } from '@aws-cdk/core';
import { CfnUserPool } from './cognito.generated';

/**
Expand Down Expand Up @@ -121,28 +121,34 @@ export enum UserPoolAttribute {
}

/**
* Methods of user sign-in
* The attributes (and aliases) that users of this pool can use to sign up or sign in.
*/
export enum SignInType {
/**
* End-user will sign in with a username, with optional aliases
*/
USERNAME,
export enum SignInAlias {
/** Sign up or sign in with a username */
USERNAME = 'username',

/**
* End-user will sign in using an email address
*/
EMAIL,
/** Sign up or sign in with an email address */
EMAIL = 'email',

/**
* End-user will sign in using a phone number
*/
PHONE,
/** Sign up or sign in with a phone number */
PHONE = 'phone_number',

/**
* End-user will sign in using either an email address or phone number
* Sign in with a secondary username, that can be set and modified after sign up.
* Can only be used in conjunction with `USERNAME`.
*/
EMAIL_OR_PHONE
PREFERRED_USERNAME = 'preferred_username'
}

/**
* The set of attributes that can be automatically verified for users in a user pool.
*/
export enum AutoVerifiedAttrs {
/** email address */
EMAIL = 'email',

/** phone number */
PHONE = 'phone_number',
}

export interface UserPoolTriggers {
Expand Down Expand Up @@ -327,28 +333,27 @@ export interface UserPoolProps {
readonly smsRoleExternalId?: string;

/**
* Method used for user registration & sign in.
* Methods in which a user registers or signs in to a user pool.
* Allows either username with aliases OR sign in with email, phone, or both.
*
* @default SignInType.Username
*/
readonly signInType?: SignInType;

/**
* Attributes to allow as username alias.
* Only valid if signInType is USERNAME
* Read the sections on usernames and aliases to learn more -
* https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html
*
* To match with 'Option 1' in the above link, with a verified email, this property should be set to `[ UsernameAlias.USERNAME,
* UsernameAlias.EMAIL ]`. To match with 'Option 2' in the above link with both a verified email and phone number, this property should be set to
* `[ UsernameAlias.EMAIL, UsernameAlias.PHONE ]`.
*
* @default - No alias.
* @default SignInAlias.USERNAME
*/
readonly usernameAliasAttributes?: UserPoolAttribute[];
readonly signInAliases?: SignInAlias[];

/**
* Attributes which Cognito will automatically send a verification message to.
* Must be either EMAIL, PHONE, or both.
* Attributes which Cognito will look to verify automatically upon user sign up.
* EMAIL and PHONE are the only available options.
*
* @default - No auto verification.
* @default - If `signInAliases` include email and/or phone, they will be included in `autoVerifiedAttributes` by default.
*/
readonly autoVerifiedAttributes?: UserPoolAttribute[];
readonly autoVerifiedAttributes?: AutoVerifiedAttrs[];

/**
* Lambda functions to use for supported Cognito triggers.
Expand All @@ -358,28 +363,9 @@ export interface UserPoolProps {
readonly lambdaTriggers?: UserPoolTriggers;
}

export interface UserPoolAttributes {
/**
* The ID of an existing user pool
*/
readonly userPoolId: string;

/**
* The ARN of the imported user pool
*/
readonly userPoolArn: string;

/**
* The provider name of the imported user pool
*/
readonly userPoolProviderName: string;

/**
* The URL of the imported user pool
*/
readonly userPoolProviderUrl: string;
}

/**
* Represents a Cognito UserPool
*/
export interface IUserPool extends IResource {
/**
* The physical ID of this user pool resource
Expand All @@ -392,41 +378,35 @@ export interface IUserPool extends IResource {
* @attribute
*/
readonly userPoolArn: string;

/**
* The provider name of this user pool resource
* @attribute
*/
readonly userPoolProviderName: string;

/**
* The provider URL of this user pool resource
* @attribute
*/
readonly userPoolProviderUrl: string;
}

/**
* Define a Cognito User Pool
*/
export class UserPool extends Resource implements IUserPool {
/**
* Import an existing user pool resource
* @param scope Parent construct
* @param id Construct ID
* @param attrs Imported user pool properties
* Import an existing user pool based on its id.
*/
public static fromUserPoolAttributes(scope: Construct, id: string, attrs: UserPoolAttributes): IUserPool {
/**
* Define a user pool which has been declared in another stack
*/
public static fromUserPoolId(scope: Construct, id: string, userPoolId: string): IUserPool {
class Import extends Resource implements IUserPool {
public readonly userPoolId = attrs.userPoolId;
public readonly userPoolArn = attrs.userPoolArn;
public readonly userPoolProviderName = attrs.userPoolProviderName;
public readonly userPoolProviderUrl = attrs.userPoolProviderUrl;
public readonly userPoolId = userPoolId;
public readonly userPoolArn = Stack.of(this).formatArn({
service: 'cognito-idp',
resource: 'userpool',
resourceName: userPoolId,
});
}
return new Import(scope, id);
}

/**
* Import an existing user pool based on its ARN.
*/
public static fromUserPoolArn(scope: Construct, id: string, userPoolArn: string): IUserPool {
class Import extends Resource implements IUserPool {
public readonly userPoolArn = userPoolArn;
public readonly userPoolId = Stack.of(this).parseArn(userPoolArn).resourceName!;
}
return new Import(scope, id);
}

Expand All @@ -442,11 +422,13 @@ export class UserPool extends Resource implements IUserPool {

/**
* User pool provider name
* @attribute
*/
public readonly userPoolProviderName: string;

/**
* User pool provider URL
* @attribute
*/
public readonly userPoolProviderUrl: string;

Expand All @@ -455,45 +437,28 @@ export class UserPool extends Resource implements IUserPool {
constructor(scope: Construct, id: string, props: UserPoolProps = {}) {
super(scope, id);

let aliasAttributes: UserPoolAttribute[] | undefined;
let usernameAttributes: UserPoolAttribute[] | undefined;

if (props.usernameAliasAttributes != null && props.signInType !== SignInType.USERNAME) {
throw new Error(`'usernameAliasAttributes' can only be set with a signInType of 'USERNAME'`);
}
let aliasAttributes: string[] | undefined;
let usernameAttributes: string[] | undefined;
let autoVerifiedAttributes: AutoVerifiedAttrs[] | undefined;

if (props.usernameAliasAttributes
&& !props.usernameAliasAttributes.every(a => {
return a === UserPoolAttribute.EMAIL || a === UserPoolAttribute.PHONE_NUMBER || a === UserPoolAttribute.PREFERRED_USERNAME;
})) {
throw new Error(`'usernameAliasAttributes' can only include EMAIL, PHONE_NUMBER, or PREFERRED_USERNAME`);
function exists(arr: any[], item: any) {
return arr.indexOf(item) > -1;
}

if (props.autoVerifiedAttributes
&& !props.autoVerifiedAttributes.every(a => a === UserPoolAttribute.EMAIL || a === UserPoolAttribute.PHONE_NUMBER)) {
throw new Error(`'autoVerifiedAttributes' can only include EMAIL or PHONE_NUMBER`);
}

switch (props.signInType) {
case SignInType.USERNAME:
aliasAttributes = props.usernameAliasAttributes;
break;

case SignInType.EMAIL:
usernameAttributes = [UserPoolAttribute.EMAIL];
break;

case SignInType.PHONE:
usernameAttributes = [UserPoolAttribute.PHONE_NUMBER];
break;
if (props.signInAliases && props.signInAliases.length > 0) {
const aliases = props.signInAliases;
if (exists(aliases, SignInAlias.PREFERRED_USERNAME) && !exists(aliases, SignInAlias.USERNAME)) {
throw new Error('signInAliases must contain USERNAME if PREFERRED_USERNAME is specified');
}

case SignInType.EMAIL_OR_PHONE:
usernameAttributes = [UserPoolAttribute.EMAIL, UserPoolAttribute.PHONE_NUMBER];
break;
if (exists(aliases, SignInAlias.USERNAME)) {
aliasAttributes = [ SignInAlias.EMAIL, SignInAlias.PHONE, SignInAlias.PREFERRED_USERNAME ].filter((a) => exists(aliases, a));
} else {
usernameAttributes = [ SignInAlias.EMAIL, SignInAlias.PHONE ].filter((ua) => exists(aliases, ua));
}

default:
aliasAttributes = props.usernameAliasAttributes;
break;
autoVerifiedAttributes = props.autoVerifiedAttributes ??
[ AutoVerifiedAttrs.EMAIL, AutoVerifiedAttrs.PHONE ].filter((a) => exists(aliases, a));
}

if (props.lambdaTriggers) {
Expand Down Expand Up @@ -539,7 +504,7 @@ export class UserPool extends Resource implements IUserPool {
userPoolName: props.userPoolName,
usernameAttributes,
aliasAttributes,
autoVerifiedAttributes: props.autoVerifiedAttributes,
autoVerifiedAttributes,
lambdaConfig: Lazy.anyValue({ produce: () => this.triggers }),
smsConfiguration: this.smsConfiguration(props),
adminCreateUserConfig,
Expand Down Expand Up @@ -710,4 +675,4 @@ export class UserPool extends Resource implements IUserPool {
};
}
}
}
}
5 changes: 1 addition & 4 deletions packages/@aws-cdk/aws-cognito/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,7 @@
},
"awslint": {
"exclude": [
"from-method:@aws-cdk/aws-cognito.UserPool",
"from-arn:UserPool.fromUserPoolArn",
"docs-public-apis:@aws-cdk/aws-cognito.IUserPool",
"no-unused-type:@aws-cdk/aws-cognito.UserPoolAttribute",
Comment thread
eladb marked this conversation as resolved.
"props-default-doc:@aws-cdk/aws-cognito.UserPoolTriggers.verifyAuthChallengeResponse",
"props-default-doc:@aws-cdk/aws-cognito.UserPoolTriggers.userMigration",
"props-default-doc:@aws-cdk/aws-cognito.UserPoolTriggers.preTokenGeneration",
Expand All @@ -105,7 +103,6 @@
"docs-public-apis:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientClientSecret",
"docs-public-apis:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientId",
"docs-public-apis:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientName",
"docs-public-apis:@aws-cdk/aws-cognito.UserPoolAttributes",
"docs-public-apis:@aws-cdk/aws-cognito.UserPoolClientProps"
]
},
Expand Down
Loading