【AWS×Webアプリ】CloudFront+Cognito認証（Terraform）

## 目的
・AWS上の静的Webサイトホスティングを有効にしたS3をCloudFrontで公開。
・Cognito認証を実装。

## 前提条件
・Terraformを使用してAWS上にリソースを作成する。
・Lambda@Edgeを使用して実装する。
・作成する内容は以下と同様

https://qiita.com/Haruki-N/items/0905f7851a32e2dc0379

## TFファイル
```terraform:aws_cloudfront.tf（前回からの追加箇所のみ抜粋）
resource "aws_cloudfront_distribution" "WebBucketDistribution" {
  enabled = true
  default_root_object = "index.html"
  price_class = "PriceClass_200"

  origin {
    origin_id                = "WebBucketOrigin"
    domain_name              = aws_s3_bucket.WebBucket.bucket_regional_domain_name
    origin_access_control_id = aws_cloudfront_origin_access_control.WebBucketOriginAccessControl.id
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }

  default_cache_behavior {
    target_origin_id       = "WebBucketOrigin"
    viewer_protocol_policy = "redirect-to-https"
    compress = true
    cached_methods         = ["GET", "HEAD"]
    allowed_methods        = ["GET", "HEAD"]

    lambda_function_association {
      event_type   = "viewer-request"
      lambda_arn   = "${aws_lambda_function.AuthCognito.arn}:${aws_lambda_function.AuthCognito.version}"
    }

    cache_policy_id = aws_cloudfront_cache_policy.WebBucketCachePolicy.id
    origin_request_policy_id = aws_cloudfront_origin_request_policy.WebBucketOriginRequestPolicy.id
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }
}
```

```terraform:aws_iam.tf（前回からの追加箇所のみ抜粋）
resource "aws_iam_role" "auth_cognito_lambda_role" {
  name = "AuthCognitoLambdaRole"

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

resource "aws_iam_role_policy" "auth_cognito_lambda_policy" {
  role = aws_iam_role.auth_cognito_lambda_role.id

  policy = jsonencode({
    Statement = [
      {
        Action = ["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"]
        Effect = "Allow"
        Resource = ["arn:aws:logs:*:*:*"]
      },
      {
        Action = ["lambda:GetFunction","lambda:EnableReplication*","iam:CreateServiceLinkedRole"]
        Effect = "Allow"
        Resource = "*"
      },
    ]
  })
}
```

```terraform:aws_lambda.tf（前回からの追加箇所のみ抜粋）
data "archive_file" "lambda_auth_cognito" {
  type        = "zip"
  source_file = "${path.module}/lambda/AuthCognito"
  output_path = "${path.module}/lambda_zip/auth_cognito.zip"
}

resource "aws_lambda_function" "AuthCognito" {
  filename         = "${path.module}/lambda_zip/auth_cognito.zip"
  function_name    = "auth_cognito"
  role             = aws_iam_role.auth_cognito_lambda_role.arn
  handler          = "auth_cognito.handler"
  runtime          = "nodejs18.x"
  source_code_hash = filebase64sha256("${path.module}/lambda/AuthCognito/auth_cognito.js")

  timeout = 5
  
  tracing_config {
    mode = "Active"
  }

  publish = true
}
```

```terraform:aws_cognito.tf
resource "aws_cognito_user_pool" "WebBucketUserPool" {
  name = "terraform-user-pool"

  admin_create_user_config {
    allow_admin_create_user_only = false
  }

  alias_attributes = ["email","preferred_username"]
  auto_verified_attributes = ["email"]

  software_token_mfa_configuration {
    enabled = true
  }

  mfa_configuration = "ON"

  password_policy {
    minimum_length = 8
    require_uppercase = true
    require_lowercase = true
    require_numbers = true
    require_symbols = true
    temporary_password_validity_days = 7
  }

  schema {
    name = "email"
    attribute_data_type = "String"
    mutable = true
    required = true
  }

  schema {
    name = "preferred_username"
    attribute_data_type = "String"
    mutable = true
    required = false
  }

  schema {
    name = "name"
    attribute_data_type = "String"
    mutable = true
    required = true
    string_attribute_constraints {
      min_length = 1
      max_length = 100
    }
  }

  verification_message_template {
    default_email_option = "CONFIRM_WITH_LINK"
    email_subject = "メールアドレスの確認"
    email_message = "あなたのメールアドレスを確認するには、以下のリンクをクリックしてください: {####}"
  }
}

resource "aws_cognito_user_pool_domain" "WebBucketUserPoolDomain" {
  domain = "terraform-${data.aws_caller_identity.self.account_id}"
  user_pool_id = aws_cognito_user_pool.WebBucketUserPool.id
}

resource "aws_cognito_user_pool_client" "WebBucketUserPoolClient" {
  name = "terraform-client"
  user_pool_id = aws_cognito_user_pool.WebBucketUserPool.id

  callback_urls = ["https://${aws_cloudfront_distribution.WebBucketDistribution.domain_name}"]
  supported_identity_providers = ["COGNITO"]

  allowed_oauth_flows = ["code"]
  allowed_oauth_flows_user_pool_client = true
  allowed_oauth_scopes = ["openid"]
}
```

◆aws_cloudfront.tf
・WebBucketDistribution（lambda_function_association）：Lambda@Edgeの関連付けを追加

◆aws_iam.tf
・auth_cognito_lambda_role：Cognito認証Lambda用IAMロール
・auth_cognito_lambda_policy：「auth_cognito_lambda_role」に付与するポリシー

◆aws_lambda.tf
・lambda_auth_cognito：Lambda関数用のファイルをZip圧縮
・AuthCognito：Lambda関数定義

◆aws_cognito.tf
・WebBucketUserPool：Cognitoユーザプール
・WebBucketUserPoolDomain：Cognitoユーザプールドメイン
・WebBucketUserPoolClient：Cognitoユーザプールクライアント

## Lambda
```auth_cognito.js
const { Authenticator } = require('cognito-at-edge');

const authenticator = new Authenticator({
  region: 'user pool regionを指定',
  userPoolId: 'user pool IDを指定',
  userPoolAppId: 'user pool app client IDを指定',
  userPoolDomain: 'user pool domainを指定',
});

exports.handler = async (request) => authenticator.handle(request);
```

## 動作確認
①Webサイトへアクセス
・サインイン画面が表示されることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/4a6b48aa-5d26-a95e-608a-1f9d21367bb9.png)

②サインアップ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/b8f86250-f9ef-a306-6252-1eb35db5e43d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/a05d61d0-0881-a1a0-92ae-d06f375980d4.png)

③メールアドレスの検証
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/c1700e1f-cf5a-bc0b-f7cc-390bc4599c8f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/088976bc-885c-b57b-8dc2-09d667f0adce.png)

④サインイン
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/3d302b00-d401-4684-f8a8-3d0925197767.png)

⑤MFAの登録
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/5df906ca-3a53-cc30-c1e6-c35945268f43.png)

⑥Webサイトへアクセス（完了）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/ce755234-4e40-ca95-df61-721c06d8fcc8.png)

⑦MFA（次回サインイン後から）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637963/3824153a-4359-94b1-abb6-ffb553878db0.png)

## 参考（前回記事）
https://qiita.com/Haruki-N/items/63799208a4a7050d0e57