How to add Amazon Cognito Auth to a Web App (part 2)
Terraform code samples that demonstrate how to set up Amazon Cognito Auth in a Web App.
Intro
In the last blog post, I talked through the concepts of how Amazon Cognito works in a Web App.
In this post, I will explain how Amazon Cognito can be programmatically set up using Terraform — so you can build cool stuff like this 👇
Creating Amazon Cognito with Terraform
I have a Wrapper.js template that you can use to spin up an implementation of Cognito, here are some highlight files from that template:
I use Terraform as the tool to create the majority of the cloud infrastructure.
main.tf
This is the core file that creates all the terraform resources, I’ll go deeper into lines 7–12 and 42–52, which are used to create Cognito and the Secrets Manager that is used to pass the Cognito details to both the Back End and Front End.
module "nextjs_app_s3" {
source = "./modules/s3"
bucket = var.domain_name
acl = "public-read"
}
module "acm" {
source = "./modules/acm"
root_domain_name = var.root_domain_name
domain_name = var.domain_name
region = "us-east-1"
}
module "nextjs_app_cloudfront" {
source = "./modules/cloudfront"
hosted_zone_name = var.root_domain_name
domain_name = var.domain_name
# lambda_edge_qualified_arn = module.uri_redirect_edge_lambda.qualified_arn
acm_cert_arn = module.acm.cert_arn
acm_cert_id = module.acm.cert_id
s3_website_endpoint = module.nextjs_app_s3.website_endpoint
default_root_object = "index.html"
}
module "rest_api_gateway" {
source = "./modules/restApiGateway"
certificate_arn = module.acm.cert_arn
root_domain_name = var.root_domain_name
domain_name = "api.${var.domain_name}"
certificate_id = module.acm.cert_id
stage_name = var.stage
cognito_arn = module.cognito.user_pool_client_arn
}
module "cognito" {
source = "./modules/cognito"
domain_name = var.domain_name
stage_name = var.stage
service_name = var.service_name
}
module "secrets_manager" {
source = "./modules/secretsManager"
service_name = var.service_name
cognito_identity_pool_id = module.cognito.identity_pool_id
cognito_user_pool_id = module.cognito.user_pool_id
cognito_user_pool_client_id = module.cognito.user_pool_client_id
cognito_user_pool_client_arn = module.cognito.user_pool_client_arn
cognito_authorizer = module.rest_api_gateway.cognito_authorizer
api_gateway_rest_api_id = module.rest_api_gateway.rest_api_id
api_gateway_root_resource_id = module.rest_api_gateway.root_resource_id
}
modules/cognito/main.tf
This is the file that creates the Amazon Cognito configuration.
Here you can see how the User Pool (lines 1–3) and Identity Pool (lines 11–20) are configured to allow authenticated users access to aws resources.
resource "aws_cognito_user_pool" "this" {
name = "${var.service_name}-${var.stage_name}-pool"
}
resource "aws_cognito_user_pool_client" "this" {
name = "${var.service_name}-${var.stage_name}-client"
user_pool_id = aws_cognito_user_pool.this.id
}
resource "aws_cognito_identity_pool" "this" {
identity_pool_name = "${var.service_name}_${var.stage_name}_identity_pool"
allow_unauthenticated_identities = false
cognito_identity_providers {
client_id = aws_cognito_user_pool_client.this.id
provider_name = "cognito-idp.eu-west-2.amazonaws.com/${aws_cognito_user_pool.this.id}"
server_side_token_check = false
}
}
resource "aws_iam_role" "authenticated" {
name = "${var.service_name}-${var.stage_name}-cognito_authenticated"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "${aws_cognito_identity_pool.this.id}"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
EOF
}
resource "aws_iam_role_policy" "authenticated" {
name = "${var.service_name}-${var.stage_name}-authenticated_policy"
role = aws_iam_role.authenticated.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"mobileanalytics:PutEvents",
"cognito-sync:*",
"cognito-identity:*"
],
"Resource": [
"*"
]
}
]
}
EOF
}
resource "aws_cognito_identity_pool_roles_attachment" "this" {
identity_pool_id = aws_cognito_identity_pool.this.id
roles = {
authenticated = aws_iam_role.authenticated.arn
}
}
modules/cognito/outputs.tf
After creating your Amazon Cognito configuration, the outputs file allows you to export variables to be used in other parts of the Terraform configuration.
This outputs file generates 4 variables that will enable the Front End and Back End to interact with Amazon Cognito to generate and use JSON Web Tokens (JWT’s).
output "identity_pool_id" {
value = aws_cognito_identity_pool.this.id
}
output "user_pool_id" {
value = aws_cognito_user_pool.this.id
}
output "user_pool_client_id" {
value = aws_cognito_user_pool_client.this.id
}
output "user_pool_client_arn" {
value = aws_cognito_user_pool.this.arn
}
modules/restApiGateway/authorizer.tf
One of the important resources that will allow the Back End to leverage Cognito Auth is an API Gateway Authorizer.
Here you can see one the 4 variables, the user pool arn — that is used to create an authorizer for the REST API Gateway, that is configured to interact with the Cognito User Pool we just generated.
resource "aws_api_gateway_authorizer" "this" {
name = var.domain_name
rest_api_id = aws_api_gateway_rest_api.this.id
type = "COGNITO_USER_POOLS"
provider_arns = [var.cognito_arn]
}
modules/restApiGateway/outputs.tf
Once this API Gateway Authorizer is created, we then need to output some configuration variables so that it can be saved into an AWS Secret (next step) and retrieved by Serverless Framework for the Back End Auth.
output "root_resource_id" {
value = aws_api_gateway_rest_api.this.root_resource_id
}
output "rest_api_id" {
value = aws_api_gateway_rest_api.this.id
}
output "cognito_authorizer" {
value = aws_api_gateway_authorizer.this.id
}
modules/secretsManager/main.tf
Lastly, we create an AWS Secret with all the exported outputs, so that the Front End and Back End have the configurations they need in order to interact with Amazon Cognito.
resource "aws_secretsmanager_secret" "this" {
resource "aws_secretsmanager_secret" "this" {
name = "${var.service_name}-tf"
description = "Generated by Terraform for ${var.service_name}"
recovery_window_in_days = 0
}
resource "aws_secretsmanager_secret_version" "this" {
secret_id = aws_secretsmanager_secret.this.id
secret_string = jsonencode(local.secrets)
}
locals {
secrets = merge(
var.secrets,
{
next_cognito_identity_pool_id = "${var.cognito_identity_pool_id}"
next_sls_cognito_user_pool_id = "${var.cognito_user_pool_id}"
next_sls_cognito_user_pool_client_id = "${var.cognito_user_pool_client_id}"
sls_cognito_authorizer = "${var.cognito_authorizer}"
sls_cognito_user_pool_client_arn = "${var.cognito_user_pool_client_arn}"
sls_api_gateway_root_resource_id = "${var.api_gateway_root_resource_id}"
sls_api_gateway_rest_api_id = "${var.api_gateway_rest_api_id}"
},
)
}
Conclusion
So this is how you use Terraform to create the Amazon Cognito resource and export its variables into a secret that can be used on the Front End and the Back End.
For the full codebase, check out this link and see this documentation for further details on the wrapper.js template!
In the next post, I’ll talk through how to use these secrets on the Front End!
Until then, I hope this has been helpful and have fun :D
How to add Amazon Cognito Auth to a Web App (part 2) was originally published in AWS in Plain English on Medium, where people are continuing the conversation by highlighting and responding to this story.