DEV Community

James Miller
James Miller

Posted on • Originally published at jamesmiller.blog on

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
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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" {
  # Fill in the name you gave to your secret
  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}"
    },
  )
}
Enter fullscreen mode Exit fullscreen mode

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


Top comments (0)