理系学生日記

おまえはいつまで学生気分なのか

CognitoのHosted UIを独自ドメインでホストするところまでをTerraformで構築する

かなり沼にハマりましたが、TerraformでAmazon CognitoのHosted UIを独自ドメインでサーブするところまでを構築できました。

実装はこちら。

構築のハマりどころ

構築自体は、aws_cognito_user_poolaws_cognito_user_pool_clientの双方を設定すればそれほど時間がかかるわけではありません。 かなりハマったのは、独自ドメインの設定でした。

Hosted UIに割り当てる証明書はバージニアで作成する

CognitoのHosted UIはCloudFront経由でサーブされます。

CloudFrontにACMで発行された証明書を割り当てる場合、当該の証明書はバージニア(us-east-1)で構成しなければなりません。 以下はCloudFrontのマニュアルから。

To use an ACM certificate with CloudFront, make sure you request (or import) the certificate in the US East (N. Virginia) Region (us-east-1).

Requirements for using SSL/TLS certificates with CloudFront

同様のことが、Cognitoのマニュアルにも記載されています。

You must change the AWS region to US East (N. Virginia) in the ACM console before you request or import a certificate.

Adding a custom domain to a user pool

これをTerraformで実現するためには、まずus-east-1用のProviderを作成しなければなりません。

provider "aws" {
  alias  = "acm_provider"
  region = "us-east-1"
}

その上で、当該リージョンにおいて、証明書やその検証用のRoute 53レコードを作成していく必要があります。 provider = aws.acm_providerあたりがその記述ですね。

resource "aws_acm_certificate" "this" {
  provider = aws.acm_provider

  # 最後にピリオドをつけてはいけない
  domain_name       = local.hosted_ui_fqdn
  validation_method = "DNS"

  options {
    certificate_transparency_logging_preference = "ENABLED"
  }

  lifecycle {
    create_before_destroy = true
  }
}

# 証明書発行時の検証用に利用する DNS レコード
resource "aws_route53_record" "validation" {
  provider = aws.acm_provider

  for_each = {
    for dvo in aws_acm_certificate.this.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  zone_id         = data.aws_route53_zone.this.id
  name            = each.value.name
  type            = each.value.type
  ttl             = 60
  records         = [each.value.record]
  allow_overwrite = true
}

resource "aws_acm_certificate_validation" "this" {
  provider = aws.acm_provider

  certificate_arn         = aws_acm_certificate.this.arn
  validation_record_fqdns = [for record in aws_route53_record.validation : record.fqdn]
}

Hosted UIに割り当てる独自ドメインの1つ上の階層にAレコードが必要

これは本当に意味がわかっていないのですが、Hosted UIに割り当てるドメインの1つ上のドメイン階層に、Aレコードが存在していることが必要です。

If you enter auth.example.com, you need an A record for example.com

If you enter auth.qa.example.com, you need an A record for qa.example.com

If you enter foo.bar.qa.example.com you need an A record for bar.qa.example.com

AWSのマニュアルでは以下のように記述されています。

A web domain that you own. Its root must have a valid A record in DNS. For example, if your custom domain is auth.example.com, you must be able to resolve example.com to an IP address.

Using your own domain for the hosted UI

なぜこのAレコードが前提になっているのが本当に意味がわからないのですが、とにかくどんな値であってもAレコードがあればOKです。 ぼくはこんな形で、127.0.0.1へ向けたダミーのAレコードを追加して回避しました。

# `custom_domain` に紐づく DNS ゾーンに作成するダミーの A レコード
# 独自ドメインを設定する前提条件として、この A レコードが必要。
# 
# see: https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html#cognito-user-pools-add-custom-domain-adding
resource "aws_route53_record" "dummy" {
  count = var.create_dummy_record ? 1 : 0

  zone_id = data.aws_route53_zone.this.id
  name    = var.custom_domain
  type    = "A"
  ttl     = 60
  records = ["127.0.0.1"]
}

ログインしてみる

今回はクライアントがSPAであることを想定し、Authorization Code Grantを利用する形にしました。

resource "aws_cognito_user_pool_client" "name" {
  name         = "client"
  user_pool_id = aws_cognito_user_pool.this.id

  # SPA なので、クライアントシークレットを発行したとしてもセキュアに守れない
  generate_secret = false

  prevent_user_existence_errors = "ENABLED"

  token_validity_units {
    access_token  = "minutes"
    id_token      = "minutes"
    refresh_token = "days"
  }
  access_token_validity = 60 # 分
  id_token_validity     = 60 # 分

  callback_urls = [
    "http://localhost:8080/"
  ]

  allowed_oauth_flows = ["code"]
  explicit_auth_flows = [
    "ALLOW_USER_SRP_AUTH",
    "ALLOW_REFRESH_TOKEN_AUTH"
  ]
  supported_identity_providers = [
    "COGNITO"
  ]
  allowed_oauth_scopes                 = ["openid"]
  allowed_oauth_flows_user_pool_client = true
}

このクライアントからHosted UIに接続する形で実際にログインをしてみます。

Hosted UI経由でログインしてみると、ログイン後たしかに認可コードが発行されていることが確認できました。