EcAuthDocs

外部IdP UserInfo透過取得API 要件定義書

外部 IdP からのユーザー情報取得 API の挙動と契約、最小限ユーザー情報の取得方針。

1. 概要

EcAuthに「外部IdPのUserInfo情報を透過的に取得するAPI」を追加します。Auth0のManagement APIと異なり、より簡潔なインターフェースで、EcAuthのsubjectと外部IdPのprovider nameを指定するだけで、外部IdPのユーザー情報を取得できるようにします。


2. 背景と目的

2.1 現状の課題

  • PR #119: 現在のUserInfo endpointは、EcAuth内部のsubjectのみを返却(個人情報保護法準拠)
  • 制約: EC-CUBEなどのクライアントアプリが外部IdP(Google、LINE等)のユーザー詳細情報を取得できない
  • ユースケース: プロフィール画面でのユーザー情報表示、メールアドレス確認など

2.2 Auth0との比較

Auth0の方式:

# 1. Management API トークン取得
curl -X POST "https://{domain}.auth0.com/oauth/token" \
  -d '{"grant_type":"client_credentials",...}'

# 2. ユーザー情報取得
curl -H "Authorization: Bearer {token}" \
  "https://{domain}.auth0.com/api/v2/users/{user_id}"

EcAuthの提案方式 (よりシンプル):

# 1回のリクエストで完結
curl -H "Authorization: Bearer {access_token}" \
  "https://ecauth.example.com/v1/api/external-userinfo?provider=google-oauth2"

3. 機能要件

3.1 エンドポイント仕様

エンドポイント

GET /v1/api/external-userinfo

リクエストパラメータ

パラメータ 必須 説明
provider string はい 外部IdPのprovider name(open_id_provider.name) google-oauth2, federate-oauth2

リクエストヘッダー

Authorization: Bearer {access_token}

レスポンス(成功時: 200 OK)

{
  "sub": "external-idp-subject-123",
  "email": "user@example.com",
  "email_verified": true,
  "name": "山田太郎",
  "given_name": "太郎",
  "family_name": "山田",
  "picture": "https://example.com/avatar.jpg",
  "locale": "ja",
  "provider": "google-oauth2"
}

※外部IdPから取得した生のクレーム情報をそのまま返却

エラーレスポンス

401 Unauthorized - アクセストークンが無効

{
  "error": "invalid_token",
  "error_description": "アクセストークンが無効または期限切れです。"
}

404 Not Found - 指定されたproviderが存在しない、または外部IdP連携がない

{
  "error": "provider_not_found",
  "error_description": "指定されたproviderが見つかりません。"
}

500 Internal Server Error - 外部IdPへのリクエスト失敗

{
  "error": "external_idp_error",
  "error_description": "外部IdPからユーザー情報を取得できませんでした。"
}

4. 技術設計

4.1 処理フロー

1. クライアント → EcAuth /v1/api/external-userinfo?provider=google-oauth2
   ├─ Authorization: Bearer {access_token}

2. EcAuth: アクセストークン検証
   ├─ TokenService.ValidateAccessTokenAsync()
   ├─ EcAuthUser.Subject を取得

3. EcAuth: 外部IdPマッピング取得
   ├─ ExternalIdpMapping テーブルから検索
   │  WHERE ecauth_subject = {subject}
   │  AND external_provider = {provider}
   ├─ external_subject を取得

4. EcAuth: OpenIdProvider設定取得
   ├─ OpenIdProvider テーブルから検索
   │  WHERE name = {provider}
   ├─ userinfo_endpoint を取得

5. EcAuth → 外部IdP UserInfo endpoint
   ├─ 外部IdPのアクセストークンを取得(後述)
   ├─ GET {userinfo_endpoint}
   │  Authorization: Bearer {external_access_token}

6. 外部IdP → EcAuth: ユーザー情報返却

7. EcAuth → クライアント: ユーザー情報転送

4.2 外部IdPアクセストークンの取得方法

重要な課題: 外部IdPのアクセストークンは現在保存していない

オプション1: アクセストークンの保存(推奨)

新規テーブル: external_idp_token

CREATE TABLE external_idp_token (
  id INT IDENTITY(1,1) PRIMARY KEY,
  ecauth_subject NVARCHAR(255) NOT NULL,
  external_provider NVARCHAR(100) NOT NULL,
  access_token NVARCHAR(2048) NOT NULL,
  refresh_token NVARCHAR(2048),
  expires_at DATETIMEOFFSET NOT NULL,
  created_at DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET(),
  updated_at DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET(),
  CONSTRAINT FK_ExternalIdpToken_EcAuthUser FOREIGN KEY (ecauth_subject) REFERENCES ecauth_user(subject),
  INDEX IX_ExternalIdpToken_Subject_Provider (ecauth_subject, external_provider)
);

保存タイミング: AuthorizationCallbackController::ExternalCallback()でJITプロビジョニング時

利点:

  • リアルタイムでユーザー情報を取得可能
  • トークン更新(リフレッシュトークン)に対応可能

課題:

  • 個人情報保護法の観点から検討が必要(アクセストークン自体には個人情報が含まれる可能性)
  • トークン有効期限管理が必要

オプション2: プロキシ方式(代替案)

クライアントが外部IdPに直接アクセスする方式(EcAuthは仲介のみ)

利点:

  • EcAuth側でトークン保存不要
  • 個人情報保護法のリスクが低い

課題:

  • クライアント側の実装が複雑化
  • セキュリティリスク(クライアントが外部IdP情報を知る必要がある)

5. 個人情報保護法への準拠

5.1 法的考察

現在の設計(PR #119):

  • UserInfo endpoint: subjectのみ返却
  • 個人情報を保存しない設計

新API追加の影響:

  • 外部IdPトークンの保存: アクセストークン自体には個人情報は含まれない(通常はランダム文字列)
  • ユーザー情報の転送: EcAuthは中継のみ(キャッシュしない)
  • 法的判断: アクセストークンは「個人情報」には該当しない可能性が高い

5.2 推奨アプローチ

  1. トークンの一時保存のみ

    • 有効期限付き(外部IdPに準拠)
    • 期限切れ後は自動削除
  2. ユーザー情報のキャッシュなし

    • 外部IdPから取得した情報はそのまま転送
    • EcAuth側でのDB保存なし
  3. プライバシーポリシーでの明記

    • 外部IdPアクセストークンを一時保存することを明記
    • ユーザーの同意取得

6. 実装計画

6.1 実装ステップ

Phase 1: 基盤整備

  1. ExternalIdpTokenモデル作成

    • エンティティクラス
    • DBマイグレーション
  2. IExternalIdpTokenServiceインターフェース作成

    • SaveTokenAsync()
    • GetTokenAsync()
    • RefreshTokenAsync()
    • CleanupExpiredTokensAsync()

Phase 2: トークン保存機能

  1. AuthorizationCallbackController拡張
    • JITプロビジョニング時にトークン保存
    • リフレッシュトークンの保存

Phase 3: 透過取得API実装

  1. ExternalUserInfoController作成

    • GET /v1/api/external-userinfo
    • パラメータ検証
    • 外部IdP UserInfo取得ロジック
  2. IExternalUserInfoServiceインターフェース作成

    • GetExternalUserInfoAsync()
    • 外部IdPへのHTTPリクエスト処理

Phase 4: テストと検証

  1. ユニットテスト作成

    • ExternalIdpTokenServiceTests
    • ExternalUserInfoControllerTests
  2. 統合テスト作成

    • 外部IdP連携テスト
    • トークン更新テスト
  3. E2Eテスト拡張

    • フェデレーション認証 + 外部UserInfo取得

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

7.1 トークン管理

  • 暗号化: 外部IdPトークンのDB保存時に暗号化を検討
  • 有効期限管理: 期限切れトークンの自動削除
  • リフレッシュトークン: 安全な更新メカニズム

7.2 アクセス制御

  • Bearer Token認証: EcAuthのアクセストークン必須
  • マルチテナント: Organization単位でのデータ分離
  • レート制限: DoS攻撃防止のための制限検討

8. マイルストーン

MVP(Minimum Viable Product)

Phase 2

Phase 3(オプション)


9. 関連ドキュメント


10. 実装後の利用例

// EC-CUBEプラグインでの利用例
async function fetchExternalUserInfo(accessToken: string, provider: string) {
  const response = await fetch(
    `https://ecauth.example.com/v1/api/external-userinfo?provider=${provider}`,
    {
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    }
  );

  if (!response.ok) {
    throw new Error('Failed to fetch external user info');
  }

  const userInfo = await response.json();

  // ユーザープロフィール画面に表示
  displayUserProfile({
    name: userInfo.name,
    email: userInfo.email,
    avatar: userInfo.picture
  });
}

以上の要件定義に基づいて実装を進めることで、Auth0より簡潔で使いやすい外部IdP UserInfo透過取得APIを提供できます。