🔀

www有無でCORSエラーが発生する問題と、Amplify + S3 + CloudFrontによるリダイレクト構築

に公開

はじめに

本番環境で断続的に発生していたネットワークエラーを調査していたところ、www.example.comexample.com のOrigin不一致によるCORSエラーが原因のひとつであることが判明しました。

この記事では、www有無の問題がどのように発覚し、AWS Amplify Hostingの制約を踏まえてどのようにリダイレクトを構築したかを紹介します。

課題:同じサイトなのにAPIが使えないユーザーがいる

Sentryで TypeError: Load failed(Safari)や TypeError: Failed to fetch(Chrome)というエラーが3ヶ月間断続的に報告されていました。

診断ログを追加して詳細を分析したところ、エラーが発生しているイベントの url タグに注目すべき違いがありました。

# 正常なユーザー
url: https://example.com/page/xxx

# エラーが発生するユーザー
url: https://www.example.com/page/xxx

バックエンドのCORS設定は https://example.com のみを許可しており、https://www.example.com からのリクエストはプリフライトで拒否されていました。

# www なし → 200 OK
curl -X OPTIONS -H "Origin: https://example.com" https://api.example.com/api

# www あり → 400 Bad Request
curl -X OPTIONS -H "Origin: https://www.example.com" https://api.example.com/api

www有無が引き起こす問題

www.example.comexample.com はブラウザにとって別のOriginです。これにより以下の問題が同時に発生します。

問題 影響
CORS バックエンドAPIのプリフライトが拒否される
localStorage / sessionStorage データが共有されず、ログイン状態が別空間になる
Cookie スコープによっては一方にしか送信されない
Service Worker(PWA) 別Originなので登録・キャッシュが独立する
SEO Googleが重複コンテンツとしてインデックスする可能性

CORS設定に www を追加すれば暫定的にエラーは止まりますが、他の問題は残り続けます。根本的にはURLをどちらかに統一してリダイレクトする必要があります。

検討した方法と結果

方法1: Amplifyのカスタムリダイレクトルール

AWS Amplify Hostingにはリダイレクトルールの設定機能があります。

{
  "source": "https://www.example.com/<*>",
  "status": "301",
  "target": "https://example.com/<*>"
}

結果: 効かなかった。

Amplifyのドメイン設定で www サブドメインがブランチに紐づいてコンテンツを配信する設定になっている場合、サブドメインの紐づけがカスタムリダイレクトルールより優先されるため、リダイレクトに到達しません。

方法2: Amplifyからwwwサブドメインを削除

サブドメインの紐づけが原因なら、削除すればリダイレクトが効くはずです。

aws amplify update-domain-association --app-id xxx --domain-name example.com \
  --sub-domain-settings '[{"branchName":"main","prefix":""}]'

結果: SSL証明書エラーになった。

AmplifyがマネージドするCloudFrontのSSL証明書から www.example.com が外れるため、TLSハンドシェイクが失敗します。DNSは解決できてもHTTPS接続ができず、リダイレクトに到達できません。

方法3: S3 + CloudFrontでリダイレクト専用構成(採用)

Amplifyとは独立した、リダイレクト専用のインフラを構築する方法です。

採用したアーキテクチャ

ユーザー: https://www.example.com/page/xxx
  → Route53 CNAME → CloudFront(リダイレクト専用)
  → S3(RedirectAllRequestsTo)
  → 301 Location: https://example.com/page/xxx

構築手順

1. ACM証明書の発行(us-east-1)

CloudFrontで使用する証明書はus-east-1リージョンに作成する必要があります。

aws acm request-certificate \
  --domain-name www.example.com \
  --validation-method DNS \
  --region us-east-1

DNS検証レコードをRoute53に追加して発行を待ちます。

2. S3バケットの作成(リダイレクト専用)

コンテンツは一切格納せず、全リクエストをリダイレクトする設定のみ行います。

aws s3api create-bucket --bucket www.example.com --region ap-northeast-1 \
  --create-bucket-configuration LocationConstraint=ap-northeast-1

aws s3api put-bucket-website --bucket www.example.com \
  --website-configuration '{"RedirectAllRequestsTo":{"HostName":"example.com","Protocol":"https"}}'

3. CloudFrontディストリビューションの作成

S3ウェブサイトエンドポイントをオリジンに設定します。ポイントは OriginProtocolPolicy: http-only です(S3ウェブサイトホスティングはHTTPSをサポートしないため)。

aws cloudfront create-distribution --distribution-config '{
  "Aliases": {"Quantity": 1, "Items": ["www.example.com"]},
  "ViewerCertificate": {
    "ACMCertificateArn": "<証明書ARN>",
    "SSLSupportMethod": "sni-only"
  },
  "Origins": {"Quantity": 1, "Items": [{
    "Id": "S3-redirect",
    "DomainName": "www.example.com.s3-website-ap-northeast-1.amazonaws.com",
    "CustomOriginConfig": {
      "HTTPPort": 80,
      "OriginProtocolPolicy": "http-only"
    }
  }]},
  "DefaultCacheBehavior": {
    "TargetOriginId": "S3-redirect",
    "ViewerProtocolPolicy": "redirect-to-https",
    ...
  },
  "IsIPV6Enabled": true
}'

4. Route53のCNAME更新

www.example.com を新しいCloudFrontディストリビューションに向けます。

# 旧CNAME(Amplify CloudFront)を削除してから
aws route53 change-resource-record-sets --change-batch '{
  "Changes": [{"Action": "CREATE", "ResourceRecordSet": {
    "Name": "www.example.com",
    "Type": "CNAME",
    "TTL": 300,
    "ResourceRecords": [{"Value": "<新CloudFrontドメイン>"}]
  }}]
}'

ハマりポイント

CloudFrontのCNAME競合

既存のCNAMEが別のCloudFrontディストリビューション(Amplify管理)を指している状態では、新しいディストリビューションにエイリアスを設定できません(CNAMEAlreadyExists エラー)。Route53のCNAMEを先に削除してからディストリビューションを作成する必要があります。

Amplifyのドメイン設定削除の副作用

Amplifyのドメイン設定からサブドメインを削除すると、Route53のCNAMEレコードも自動的に削除される場合があります。手動で設定したDNSレコードが消えていないか確認が必要です。

CloudFrontのデプロイ時間

Status: InProgress でも最寄りのエッジロケーション(例: 東京)では既に有効になっている場合があります。ブラウザで確認して動作していれば、全エッジへのデプロイ完了(Deployed)を待つ必要はありません。

まとめ

方法 結果
Amplifyカスタムリダイレクトルール サブドメイン紐づけが優先され効かない
Amplifyからwww削除 SSL証明書から外れTLS失敗
S3 + CloudFront独立構成 正常にリダイレクト動作

www/非wwwの混在は、CORSだけでなくlocalStorage・Cookie・PWAなど広範囲に影響する問題です。Amplify Hostingを使用している場合、リダイレクト設定に制約があるため、S3 + CloudFrontによる独立した構成が確実な解決策になります。

Discussion