EcAuthDocs

EcAuth ドメインアーキテクチャ設計書

EcAuth が提供するドメイン構成、ゾーン分割、サブドメイン設計、Cloudflare 上のレコード戦略。

1. 概要

EcAuth は本番環境で独自ドメイン ec-auth.io を使用し、サブドメインベースのマルチテナンシーを実現する。各 Organization(テナント)は {tenant}.ec-auth.io の形式でアクセスされ、TenantMiddleware がホスト名からテナントを自動的に識別する。

ドメイン取得・管理

項目
ドメイン ec-auth.io
レジストラ Cloudflare
DNS 管理 Cloudflare
SSL/TLS Cloudflare Universal SSL(エッジ)+ Origin Certificate(オリジン)

2. ドメイン設計

サブドメインマッピング

サブドメイン 用途
{tenant}.ec-auth.io テナント固有の IdP エンドポイント
api.ec-auth.io テナント横断の共通 API エンドポイント(/platform/*
ec-auth.io(ルートドメイン) ランディングページ / ドキュメント / リダイレクト

例:

  • production.ec-auth.io → 本番テナント「production」の IdP
  • shopname.ec-auth.io → 店舗「shopname」の IdP
  • api.ec-auth.io/platform/v1/client-resolve → Client ID からテナントを解決

URL 構造

https://{tenant}.ec-auth.io/v1/authorization          ← B2C 認可エンドポイント
https://{tenant}.ec-auth.io/v1/token                   ← トークンエンドポイント
https://{tenant}.ec-auth.io/v1/userinfo                ← ユーザー情報エンドポイント
https://{tenant}.ec-auth.io/v1/b2b/passkey/*           ← B2B パスキー API
https://{tenant}.ec-auth.io/.well-known/openid-configuration ← OIDC ディスカバリ
https://api.ec-auth.io/platform/v1/client-resolve     ← Client ID → テナント解決(共通 API)

共通 API エンドポイント (api.ec-auth.io)

テナントコンテキストに依存しないテナント横断 API を提供する(EcAuth#342 / EcAuthDocs#58 で導入)。

項目
ホスト api.ec-auth.io
パスプレフィックス /platform/*
TenantMiddleware スキップ(テナント解決を行わない)
ルーティング先 Azure App Service の本番 IdP(*.ec-auth.io と同じオリジン)
DNS Cloudflare のワイルドカード CNAME *.ec-auth.io が自動的にカバー(追加の CNAME は不要)
Azure カスタムドメイン検証 asuid.api TXT レコードを別途追加
SSL 既存の *.ec-auth.io ワイルドカード Origin Certificate を再利用

現在提供している /platform/* エンドポイント:

  • GET /platform/v1/client-resolve?client_id=xxx{ tenant_name, base_url, organization_name }

3. ネットワークアーキテクチャ

flowchart LR
    Browser["ブラウザ / EC-CUBE"]
    CF["Cloudflare Edge
(SSL 終端, WAF, DDoS 保護)"] Azure["Azure App Service
(Origin)"] DB["Azure SQL Database"] Browser -->|HTTPS| CF CF -->|"HTTPS (Origin Certificate)"| Azure Azure --> DB

通信フロー

  1. クライアント(ブラウザ / EC-CUBE プラグイン)が https://{tenant}.ec-auth.io にリクエスト
  2. Cloudflare Edge で SSL 終端、WAF ルール適用、DDoS 保護
  3. Cloudflare が Azure App Service(オリジン)に Origin Certificate を使用して HTTPS 転送
  4. App Service 上の TenantMiddleware がホスト名からテナントを抽出
  5. レスポンスは同じ経路で返却

4. DNS アーキテクチャ

DNS レコード構成

種別 名前 プロキシ 用途
CNAME * {web_app_name}.azurewebsites.net Proxied ワイルドカード(全テナント)
CNAME @ {web_app_name}.azurewebsites.net Proxied ルートドメイン
TXT asuid {Azure 検証 ID} DNS only Azure ドメイン検証
TXT asuid.* {Azure 検証 ID} DNS only Azure ワイルドカードドメイン検証

Terraform Cloudflare Provider

# Cloudflare DNS レコード管理
resource "cloudflare_record" "wildcard" {
  zone_id = var.cloudflare_zone_id
  name    = "*"
  content = "ecauth-prod.azurewebsites.net"
  type    = "CNAME"
  proxied = true
}

resource "cloudflare_record" "root" {
  zone_id = var.cloudflare_zone_id
  name    = "@"
  content = "ecauth-prod.azurewebsites.net"
  type    = "CNAME"
  proxied = true
}

# Azure ドメイン検証用 TXT レコード
resource "cloudflare_record" "azure_verification" {
  zone_id = var.cloudflare_zone_id
  name    = "asuid"
  content = azurerm_linux_web_app.this.custom_domain_verification_id
  type    = "TXT"
  proxied = false
}

5. SSL/TLS 戦略

構成

flowchart LR
    subgraph Edge["Cloudflare Edge"]
        UniversalSSL["Universal SSL
(*.ec-auth.io)"] end subgraph Origin["Azure App Service"] OriginCert["Cloudflare Origin Certificate
(*.ec-auth.io, 15年有効)"] end Client -->|TLS 1.2+| UniversalSSL UniversalSSL -->|"TLS (Origin Cert)"| OriginCert

SSL モード: Full (strict)

レイヤー 証明書 管理方法
Edge(クライアント→Cloudflare) Cloudflare Universal SSL 自動発行・自動更新
Origin(Cloudflare→Azure) Cloudflare Origin Certificate Cloudflare ダッシュボード / API で生成、Azure にインストール

Origin Certificate の特徴

  • Cloudflare が発行する専用証明書(公的 CA ではない)
  • 最大 15 年有効
  • ワイルドカード対応(*.ec-auth.io
  • Cloudflare プロキシ経由でのみ信頼される

6. Azure App Service カスタムドメイン

カスタムドメインバインディング

# ワイルドカードカスタムドメイン
resource "azurerm_app_service_custom_hostname_binding" "wildcard" {
  hostname            = "*.ec-auth.io"
  app_service_name    = azurerm_linux_web_app.this.name
  resource_group_name = var.resource_group_name
}

# ルートドメイン
resource "azurerm_app_service_custom_hostname_binding" "root" {
  hostname            = "ec-auth.io"
  app_service_name    = azurerm_linux_web_app.this.name
  resource_group_name = var.resource_group_name
}

App Service プラン要件

プラン カスタムドメイン SSL バインディング 備考
F1 (Free) 不可 不可 -
B1 (Basic) 対応 対応 本番環境で使用
S1 (Standard) 対応 対応 -

現在の本番環境は B1 プランを使用しており、カスタムドメインと SSL バインディングに対応しています。

注意

注意: B1 プランのデプロイ時ダウンタイムについて

B1 プランにはデプロイメントスロット(ステージングスロット)がないため、デプロイ時にアプリケーションの再起動が発生する(数秒〜数十秒程度)。この間、新規ログインを試みたユーザーは認証エラーとなる

  • B2B パスキー: EC-CUBE プラグインからの API 呼び出しが 502/503 エラー
  • B2C フェデレーション: /v1/authorization または /v1/authorization/callback でエラーページ表示
  • ログイン済みユーザー: EC-CUBE セッションは独立しているため影響なし

現段階(初期段階・手動デプロイ・低トラフィック)では許容可能。テナント数・トラフィック増加時には P0v4 プラン(Premium V4 最小構成、デプロイメントスロット対応)へのアップグレードを検討すること。

Origin Certificate のインストール

Cloudflare Origin Certificate を Azure App Service にインストールする方法:

  1. Cloudflare ダッシュボードで Origin Certificate を生成(*.ec-auth.io
  2. PFX 形式に変換して Azure App Service にアップロード
  3. カスタムドメインに SSL バインディングを設定
# Origin Certificate を App Service にアップロード
resource "azurerm_app_service_certificate" "origin" {
  name                = "cloudflare-origin-cert"
  resource_group_name = var.resource_group_name
  location            = var.location
  pfx_blob            = var.origin_certificate_pfx_base64
  password            = var.origin_certificate_password
}

7. 環境別ドメインマッピング

環境 ドメイン テナント抽出方法 DEFAULT_ORGANIZATION_TENANT_NAME
Dev(ローカル Docker) localhost:8081 環境変数フォールバック dev
Staging ecauth-staging-{suffix}.azurewebsites.net 環境変数フォールバック staging
Production {tenant}.ec-auth.io サブドメイン抽出 production

Dev / Staging と Production の違い

  • Dev / Staging: Azure プラットフォームドメイン(*.azurewebsites.net)を使用。TenantMiddleware はこれらのドメインを検出して DEFAULT_ORGANIZATION_TENANT_NAME にフォールバックする。web_app_suffix が必要。
  • Production: カスタムドメイン(*.ec-auth.io)を使用。TenantMiddleware がサブドメインからテナント名を抽出する。web_app_suffix不要

8. TenantMiddleware の動作

TenantMiddleware は既にサブドメインベースのテナント識別に対応しており、コード変更は不要

抽出ロジック(TenantMiddleware.cs L33-63)

private string ExtractTenantNameFromHost(string host)
{
    // IPアドレスの場合 → 空文字列(デフォルトテナント)
    if (IPAddress.TryParse(host, out _))
        return string.Empty;

    // localhost の場合 → 空文字列(デフォルトテナント)
    if (host.Equals("localhost", StringComparison.OrdinalIgnoreCase))
        return string.Empty;

    // Azure プラットフォームドメインの場合 → 空文字列(デフォルトテナント)
    if (host.EndsWith(".azurewebsites.net", StringComparison.OrdinalIgnoreCase) ||
        host.EndsWith(".azurecontainerapps.io", StringComparison.OrdinalIgnoreCase))
        return string.Empty;

    // サブドメイン形式からテナント名を抽出
    var segments = host.Split('.');
    if (segments.Length > 2)
        return segments[0]; // ← テナント名
    return string.Empty;
}

テナント抽出の具体例

ホスト名 segments 結果
production.ec-auth.io ["production", "ec-auth", "io"] (3) "production"
shopname.ec-auth.io ["shopname", "ec-auth", "io"] (3) "shopname"
localhost - "" → 環境変数フォールバック
ecauth-staging-xxx.azurewebsites.net - "" → 環境変数フォールバック

フォールバック動作(TenantMiddleware.cs L19-28)

var tenantName = ExtractTenantNameFromHost(host);
var defaultOrganizationTenantName = Environment.GetEnvironmentVariable("DEFAULT_ORGANIZATION_TENANT_NAME") ?? string.Empty;
var finalTenantName = string.IsNullOrEmpty(tenantName) ? defaultOrganizationTenantName : tenantName;

サブドメインが抽出できない場合(localhost、Azure ドメイン)は DEFAULT_ORGANIZATION_TENANT_NAME 環境変数にフォールバックする。

9. 1Password アイテム設計

本番環境のドメインがカスタムドメインに変更されることに伴い、1Password アイテムの設計を更新する。web_app_suffix は本番環境では不要。

ecauth-prod-sql (DATABASE)

フィールド 備考
hostname ecauth-prod-sql.database.windows.net Azure SQL Database
database EcAuthDb
username ecauth_prod_admin
password (ランダム生成 32文字)

ecauth-prod-app (SECURE_NOTE)

フィールド 備考
organization_code production
organization_name Production Environment
organization_tenant_name production サブドメインと一致
client_id prod-client-id
client_secret (ランダム生成)
app_name production-app
redirect_uri https://production.ec-auth.io/v1/auth/callback カスタムドメイン使用
state_password (ランダム生成)
b2b_user_subject (UUID 生成)
b2b_user_external_id prod-admin
b2b_redirect_uri https://production.ec-auth.io/admin/ecauth/callback カスタムドメイン使用
b2b_allowed_rp_ids production.ec-auth.io,localhost カスタムドメイン使用

ステージングとの違い:

  • web_app_suffix フィールドなし(カスタムドメインのため不要)
  • URL が *.azurewebsites.net*.ec-auth.io
  • Web App 名(ecauth-prod)は Terraform 変数 var.web_app_name で管理

ecauth-prod-mockidp (SECURE_NOTE)

フィールド 備考
app_name production-mockidp
client_id production-mockclientid
client_secret (ランダム生成)

mockidp-production (PASSWORD)

フィールド 備考
base_url https://mock-openid-provider.mangoplant-f8a75293.japaneast.azurecontainerapps.io 既存 Container App 共有
authorization_endpoint {base_url}/authorization?org=production ?org=production パラメータ
token_endpoint {base_url}/token?org=production
userinfo_endpoint {base_url}/userinfo?org=production
default_user_email production-user@example.com
default_user_password (ランダム生成)

Terraform からの参照方法

# ecauth-prod-app の参照(web_app_suffix を使わない)
locals {
  op_prod_app_fields = {
    for f in data.onepassword_item.prod_app.section[0].field : f.label => f.value
  }

  # web_app_suffix は不要。Web App 名は var.web_app_name で直接指定
  # web_app_suffix = local.op_prod_app_fields["web_app_suffix"]  ← 削除
  client_id      = local.op_prod_app_fields["client_id"]
  redirect_uri   = local.op_prod_app_fields["redirect_uri"]
  # ...
}

module "web_app_identityprovider" {
  # web_app_name を直接指定(1Password に依存しない)
  web_app_name = var.web_app_name  # デフォルト: "ecauth-prod"
}

10. GitHub Actions ワークフローへの影響

現在の問題

deploy-production.ymlmigrate-production.ymlWEB_APP_SUFFIX GitHub Secret を参照して URL を組み立てている。

# 現在の deploy-production.yml (L78, L83)
app-name: ecauth-prod-${{ secrets.WEB_APP_SUFFIX }}
HEALTH_CHECK_URL: https://ecauth-prod-${{ secrets.WEB_APP_SUFFIX }}.azurewebsites.net/healthz

# 現在の migrate-production.yml (L150)
ECAUTH_BASE_URL: https://ecauth-prod-${{ secrets.WEB_APP_SUFFIX }}.azurewebsites.net

変更後

# deploy-production.yml
app-name: ecauth-prod  # 固定値
HEALTH_CHECK_URL: https://production.ec-auth.io/healthz  # カスタムドメイン

# verify ジョブの URL もカスタムドメインに変更
ECAUTH_BASE_URL: https://production.ec-auth.io
E2E_BASE_URL: https://production.ec-auth.io
DEV_B2B_ALLOWED_RP_IDS: production.ec-auth.io  # 1Password の値と一致

# migrate-production.yml
ECAUTH_BASE_URL: https://production.ec-auth.io

不要になる GitHub Secret

Secret 状態
WEB_APP_SUFFIX production 環境から削除可能

11. Terraform への影響

現在の問題(production/main.tf

# L44: web_app_suffix を 1Password から取得
web_app_suffix = local.op_prod_app_fields["web_app_suffix"]

# L145: web_app_suffix にフォールバック
web_app_name = var.web_app_name != "" ? var.web_app_name : "ecauth-prod-${local.web_app_suffix}"

変更内容

  1. web_app_suffix 依存の除去: var.web_app_name にデフォルト値 "ecauth-prod" を設定し、1Password の web_app_suffix への依存を削除
  2. Cloudflare Provider 追加: DNS レコード、Origin Certificate の管理
  3. カスタムドメインバインディング追加: azurerm_app_service_custom_hostname_binding
  4. Azure IP 制限: Cloudflare IP のみ許可(オリジン保護)

変更後のイメージ

# variables.tf
variable "web_app_name" {
  description = "Web App name"
  type        = string
  default     = "ecauth-prod"  # デフォルト値を設定
}

# main.tf
module "web_app_identityprovider" {
  web_app_name = var.web_app_name  # フォールバック不要
}

# Cloudflare DNS
resource "cloudflare_record" "wildcard" {
  zone_id = var.cloudflare_zone_id
  name    = "*"
  content = "${var.web_app_name}.azurewebsites.net"
  type    = "CNAME"
  proxied = true
}

# カスタムドメインバインディング
resource "azurerm_app_service_custom_hostname_binding" "wildcard" {
  hostname            = "*.ec-auth.io"
  app_service_name    = module.web_app_identityprovider.web_app_name
  resource_group_name = data.azurerm_resource_group.this.name
}

12. セキュリティ考慮事項

Cloudflare WAF / DDoS 保護

  • Cloudflare プロキシを有効化(Proxied モード)することで、オリジン IP を秘匿
  • Cloudflare WAF ルールによる一般的な攻撃パターンの防御
  • DDoS 保護はプラン共通で提供

オリジン IP 保護

Azure App Service のアクセス制限で、Cloudflare IP のみを許可する:

# Cloudflare IP レンジを動的に取得
data "cloudflare_ip_ranges" "this" {}

resource "azurerm_linux_web_app" "this" {
  site_config {
    dynamic "ip_restriction" {
      for_each = data.cloudflare_ip_ranges.this.ipv4_cidr_blocks
      content {
        name       = "cloudflare-ipv4-${ip_restriction.key}"
        ip_address = ip_restriction.value
        action     = "Allow"
        priority   = 100 + ip_restriction.key
      }
    }
    ip_restriction_default_action = "Deny"
  }
}

HTTPS 強制

  • Cloudflare 側: 「Always Use HTTPS」を有効化
  • Azure 側: HTTPS Only = true(既に設定済み)

HSTS

Cloudflare 側で HSTS ヘッダーを設定:

  • max-age: 31536000(1年)
  • includeSubDomains: true
  • preload: true

13. 実装チェックリスト

Phase A: 準備

Phase B: 1Password アイテム作成

Phase C: Terraform 変更(ecauth-infrastructure)

Phase D: GitHub Actions 変更(EcAuth)

Phase E: 検証

参考資料