Terraform Cognito with Lambda Triggers

Sep 12, 2022

The Problem

I have been building a side project with AWS Cognito and Terraform. I wanted a custom message lambda trigger to be invoked anytime the user signed up for my app, however I kept getting permission errors. This blog shows you the terraform configuration you need to to let cognito invoke lambda triggers with Terrafrom.

Terraform config

AWC Cognito

For AWS Cognito, I have a simple user pool that allows users to sign up with their email and a passowrd, and a single user pool client to hold users for my development environment.

resource "aws_cognito_user_pool" "garden_tour_user_pool" {
  name = "garden_tour_user_pool"

  username_attributes = ["email"]
  auto_verified_attributes = ["email"]
  password_policy {
    minimum_length = 6
    temporary_password_validity_days = 2
  }

  schema {
    attribute_data_type      = "String"
    developer_only_attribute = false
    mutable                  = true
    name                     = "email"
    required                 = true

    string_attribute_constraints {
      min_length = 1
      max_length = 256
    }
  }

  lambda_config {
    custom_message = module.lambda.lambda_arn
  }
}

resource "aws_cognito_user_pool_client" "garden_tour_client_development" {
  name = "garden_tour_client_development"

  user_pool_id = aws_cognito_user_pool.garden_tour_user_pool.id
  generate_secret = false
  refresh_token_validity = 90
  prevent_user_existence_errors = "ENABLED"
  explicit_auth_flows = [
    "ALLOW_REFRESH_TOKEN_AUTH",
    "ALLOW_USER_PASSWORD_AUTH",
  ]
}


resource "aws_lambda_permission" "allow_cognito_invoke_trigger" {
  statement_id  = "AllowExecutionFromCognito"
  action        = "lambda:InvokeFunction"
  function_name = module.lambda.lambda_function_name
  principal     = "cognito-idp.amazonaws.com"
  source_arn    = aws_cognito_user_pool.garden_tour_user_pool.arn
}

The essential resource needed is the aws_lambda_permission. Without this resource, your user pool’s lambda’s (found in lambda_config) will not be invoked. Instead, AWS Cognito will return a cryptic error message.

I have configured my Cognito user pool resource with a lambda_config. This configuration object allows you to invoke lambda when a specific cognito event occurs. You can find more information about these lambda triggers at the AWS docs.

Lambda

The lambda trigger can also be managed by Terraform. You need to create a variety of AWS resources to deploy your AWS Lambda though. I recommend follwoing along Terraform’s guide Deploy Serverless Applications with AWS Lambda and API Gateway to fully understand what resources you are creating.

In a nutshell, you are creating the following:

resource "random_pet" "s3_bucket" {
  prefix = var.s3_bucket_name
  length = 4
}

resource "aws_s3_bucket" "s3_bucket" {
  bucket = random_pet.s3_bucket.id

  force_destroy = true
}


data "archive_file" "lambda" {
  type = "zip"

  source_dir  = var.lambda_source_dir
  output_path = var.lambda_output_dir
}


resource "aws_s3_object" "s3_object" {
  bucket = aws_s3_bucket.s3_bucket.id

  key    = var.s3_object_key
  source = data.archive_file.lambda.output_path

  etag = filemd5(data.archive_file.lambda.output_path)
}

resource "aws_lambda_function" "lambda" {
  function_name = var.lambda_function_name

  s3_bucket = aws_s3_bucket.s3_bucket.id
  s3_key    = aws_s3_object.s3_object.key

  runtime = var.lambda_function_handler_runtime
  handler = var.lambda_function_handler_name

  source_code_hash = data.archive_file.lambda.output_base64sha256

  role = aws_iam_role.lambda.arn

  timeout = var.lambda_function_timeout

  environment {
    variables = {
      COGNITO_USER_POOL_ID = var.lambda_environment_variables.cognito_user_pool_id
      COGNITO_CLIENT_ID = var.lambda_environment_variables.cognito_client_id
      TABLE_NAME = var.lambda_environment_variables.table_name
      DYNAMO_REGION = var.lambda_environment_variables.dynamo_region
    }
  }

}


resource "aws_cloudwatch_log_group" "lambda" {
  name = "/aws/lambda/${aws_lambda_function.lambda.function_name}"

  retention_in_days = 30
}


resource "aws_iam_role" "lambda" {
  name = "serverless_lambda"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Sid    = ""
      Principal = {
        Service = "lambda.amazonaws.com"
      }
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "lambda_policy" {
  role       = aws_iam_role.lambda.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

Hope this helps those trying to manage their AWS Cognito lambda triggers with terraform. If you encounter issues, feel free to ping me on Twitter with questions.