# Add AWS Cognito to Existing Deployment

# Prerequisites

Before starting, gather the following information:

  • AWS Account ID: Locate this in the deployment organization's settings page.
  • Deployment ID: Found in the deployment's settings page.
  • Log in to the production SSO account, and assume the ProdAutoPilotSupportLevelTwo role in the correct AWS account.

# Locating The Correct Stack

  1. Open the CloudFormation service in the AWS console.
  2. Use the Deployment ID to filter and locate the main stack.
  3. To simplify the search, untoggle the "View nested" switch.
  4. Once the main stack is identified, proceed to update it.

# Editing The Template

The recommended method for editing the stack's template is through the "Infrastructure Composer" since you can edit it through your browser. Once in the editor, follow these steps:

  1. Navigate to the Template tab within the editor.
  2. Ensure the template remains in JSON format.

DANGER
Do not convert the template to YAML. AutoPilot requires JSON format, and converting from YAML can introduce discrepancies that prevent the deployment page from loading correctly.

Proceed to update the template.

# Add Cognito Resources

Locate the end of the 'Resources' section in the template. You'll need to add two separate resources. First add the Cognito user pool:

"CognitoUserPool": {
	"Type": "AWS::Cognito::UserPool",
	"Properties": {
		"UserPoolName": {
			"Fn::Sub": "${AWS::StackName}-user-pool"
		},
		"AutoVerifiedAttributes": [
			"email"
		],
		"UsernameAttributes": [
			"email"
		],
		"Schema": [
			{
				"Name": "email",
				"Required": true,
				"Mutable": false
			}
		],
		"Policies": {
			"PasswordPolicy": {
				"MinimumLength": 8,
				"RequireUppercase": true,
				"RequireLowercase": true,
				"RequireNumbers": true,
				"RequireSymbols": true
			}
		},
		"MfaConfiguration": "OPTIONAL",
		"EnabledMfas": [
			"SOFTWARE_TOKEN_MFA"
		],
		"AccountRecoverySetting": {
			"RecoveryMechanisms": [
				{
					"Name": "verified_email",
					"Priority": 1
				}
			]
		},
		"UserPoolAddOns": {
			"AdvancedSecurityMode": "ENFORCED"
		}
	}
},

Next add the user pool client:

"CognitoUserPoolClient": {
	"Type": "AWS::Cognito::UserPoolClient",
	"Properties": {
		"ClientName": {
			"Fn::Sub": "${AWS::StackName}-app-client"
		},
		"UserPoolId": {
			"Ref": "CognitoUserPool"
		},
		"GenerateSecret": false,
		"RefreshTokenValidity": 30,
		"AccessTokenValidity": 60,
		"IdTokenValidity": 60,
		"TokenValidityUnits": {
			"RefreshToken": "days",
			"AccessToken": "minutes",
			"IdToken": "minutes"
		},
		"ExplicitAuthFlows": [
			"ALLOW_USER_PASSWORD_AUTH",
			"ALLOW_USER_SRP_AUTH",
			"ALLOW_REFRESH_TOKEN_AUTH"
		],
		"PreventUserExistenceErrors": "ENABLED"
	}
},

NOTE
If you get errors about CognitoIdentityPool or CognitoUserPoolDomain, you can start with just the User Pool and User Pool Client above. These two resources are sufficient for basic authentication and CLI access. The Identity Pool and Domain are optional for more advanced features.

# Modify Instance Profile Role

Navigate to the InstanceProfileRole resource in the template and append the following policy statement:

{
	"PolicyName": "AllowCognitoAccess",
	"PolicyDocument": {
		"Version": "2012-10-17",
		"Statement": [
			{
				"Effect": "Allow",
				"Action": [
					"cognito-idp:AdminCreateUser",
					"cognito-idp:AdminDeleteUser",
					"cognito-idp:AdminGetUser",
					"cognito-idp:AdminListGroupsForUser",
					"cognito-idp:AdminAddUserToGroup",
					"cognito-idp:AdminRemoveUserFromGroup",
					"cognito-idp:ListUsers",
					"cognito-idp:ListGroups",
					"cognito-idp:DescribeUserPool",
					"cognito-idp:DescribeUserPoolClient",
					"cognito-idp:AdminSetUserPassword",
					"cognito-idp:AdminEnableUser",
					"cognito-idp:AdminDisableUser"
				],
				"Resource": [
					{
						"Fn::GetAtt": [
							"CognitoUserPool",
							"Arn"
						]
					}
				]
			}
		]
	}
}

# Adding Output Values

Navigate to the Outputs section of the template and add these outputs (adjust based on which resources you added):

"CognitoUserPoolId": {
	"Value": {
		"Ref": "CognitoUserPool"
	}
},
"CognitoUserPoolArn": {
	"Value": {
		"Fn::GetAtt": [
			"CognitoUserPool",
			"Arn"
		]
	}
},
"CognitoUserPoolClientId": {
	"Value": {
		"Ref": "CognitoUserPoolClient"
	}
}

# Add Metadata To Display Info In AutoPilot

Navigate to the Metadata section of the template and merge the following JSON with the JetRails::Gui::Output section:

{
	"CognitoUserPoolId": {
		"Component": "ListItem",
		"FriendlyName": "User Pool ID"
	},
	"CognitoUserPoolArn": {
		"Component": "ListItem",
		"FriendlyName": "User Pool ARN"
	},
	"CognitoUserPoolClientId": {
		"Component": "ListItem",
		"FriendlyName": "App Client ID"
	}
}

Next, merge the following JSON with the JetRails::Gui::OutputGroups section:

{
	"CognitoAuthentication": {
		"FriendlyName": "AWS Cognito",
		"Variant": "LabelValueTable",
		"Outputs": [
			"CognitoUserPoolId",
			"CognitoUserPoolArn",
			"CognitoUserPoolClientId"
		]
	}
}

# Adding User Groups

If needed, you can add user groups for role-based access control. Simply add this to your Resources section:

{
	"CognitoUserPoolGroup": {
		"Type": "AWS::Cognito::UserPoolGroup",
		"Properties": {
			"GroupName": "Admins",
			"Description": "Admin user group",
			"UserPoolId": {
				"Ref": "CognitoUserPool"
			},
			"Precedence": 1
		}
	}
}

# Enable Email Notifications

If needed, consider configuring SES for email delivery by adding the following to the Resources section (recomended for production):

{
	"CognitoUserPool": {
		"Type": "AWS::Cognito::UserPool",
		"Properties": {
			"EmailConfiguration": {
				"EmailSendingAccount": "DEVELOPER",
				"SourceArn": "arn:aws:ses:region:account-id:identity/example.com"
			}
		}
	}
}

# Applying the Changes

  1. Validate the updated template.
  2. Update the stack's new template.
  3. Use the wizard to apply the changes.

Once the update is complete, the Cognito User Pool information will be outputted in the AutoPilot interface in the Overview tab. The jump host will have access to manage Cognito users and pools via the AWS CLI.

# Verify Changes

First, SSH into the jump host and verify that you can access the Cognito User Pool:

# Get the User Pool ID from the stack outputs
USER_POOL_ID="us-east-1_z6QDTTwHU"

# Describe the user pool
aws cognito-idp describe-user-pool --user-pool-id $USER_POOL_ID

If all goes well, you should see the user pool configuration in JSON format. Otherwise, you'll see an error. If you see an error, you can try to go to the EC2 dashboard and detach/reattach the instance profile to the instance. In most cases you will not need to do that.

Next, create a test user to verify full functionality:

# Create a user
aws cognito-idp admin-create-user \
    --user-pool-id $USER_POOL_ID \
    --username nemanja.djuric@jetrails.com \
    --user-attributes Name=email,Value=nemanja.djuric@jetrails.com \
    --temporary-password "TempPass123!" \
    --message-action SUPPRESS

# Verify the user was created
aws cognito-idp admin-get-user \
    --user-pool-id $USER_POOL_ID \
    --username nemanja.djuric@jetrails.com

Next, list all users in the pool and verify the test user is present:

aws cognito-idp list-users --user-pool-id $USER_POOL_ID

Set a permanent password for the test user:

aws cognito-idp admin-set-user-password \
    --user-pool-id $USER_POOL_ID \
    --username nemanja.djuric@jetrails.com \
    --password "NewPassword123!" \
    --permanent

Finally, clean up by deleting the test user:

aws cognito-idp admin-delete-user \
    --user-pool-id $USER_POOL_ID \
    --username nemanja.djuric@jetrails.com

# Troubleshooting

Verify the IAM policy includes cognito-idp:AdminCreateUser and check the User Pool ID is correct.

Ensure MfaConfiguration is set to "OPTIONAL" or "ON" and EnabledMfas includes "SOFTWARE_TOKEN_MFA".

Verify the User Pool Domain was created successfully and check the domain URL in the outputs.