EcAuthDocs

B2Bパスキー アーキテクチャ設計書

EC-CUBE 管理画面向けの B2B パスキー認証 (WebAuthn / FIDO2) のフローと EF Core モデル設計。

概要

EC-CUBE管理画面向けのパスキー認証(WebAuthn/FIDO2)の詳細設計を定義する。

目的

  • 管理画面のフィッシング対策
  • 店舗管理者の設定負担軽減(外部IdP設定不要)
  • フリーミアム提供(5ユーザーまで無料)によるEcAuth導入促進

スコープ

  • B2Bパスキー認証API(EcAuth側)
  • EC-CUBEプラグイン連携設計
  • データモデル設計

WebAuthn/FIDO2 概要

パスキーとは

パスキー(Passkey)は、FIDO2/WebAuthn標準に基づく認証方式。公開鍵暗号を使用し、フィッシング耐性を持つ。

主要な特徴

  • フィッシング耐性: RP ID(ドメイン)検証により、偽サイトでは認証不可
  • パスワードレス: パスワードの記憶・管理が不要
  • デバイス連携: Touch ID、Windows Hello、セキュリティキー等に対応

用語

用語 説明
RP (Relying Party) 認証を要求するサービス(EC-CUBEサイト)
RP ID RPを識別するドメイン名(shop.example.com)
Credential 認証に使用する公開鍵クレデンシャル
Challenge リプレイ攻撃防止用のランダム値
SignCount 認証回数カウンター(リプレイ攻撃検出用)
Attestation デバイスの証明情報

アーキテクチャ全体像

flowchart TB
    subgraph ECCUBE["EC-CUBE サイト (shop.example.com)"]
        subgraph Plugin["EC-CUBEプラグイン"]
            P1["管理画面ログインUI拡張"]
            P2["パスキー管理画面"]
            P3["WebAuthn JavaScript API呼び出し"]
            P4["本人確認(パスワード再入力)"]
        end
        WebAuthn["navigator.credentials.*
(RP ID = shop.example.com)"] end subgraph EcAuth["EcAuth ({tenant}.ec-auth.io)"] subgraph API["B2Bパスキー API"] A1["/v1/b2b/passkey/register/options"] A2["/v1/b2b/passkey/register/verify"] A3["/v1/b2b/passkey/authenticate/options"] A4["/v1/b2b/passkey/authenticate/verify"] end subgraph DB["データベース"] D1["B2BUser"] D2["B2BPasskeyCredential"] D3["WebAuthnChallenge"] end end Plugin <--> WebAuthn ECCUBE <-->|API通信| EcAuth API <--> DB

データモデル設計

注意
注意

以下のデータモデルは概念設計を示すものです。実装時には、EF Coreの規約に従い、適切な外部キープロパティ(例: B2BUserId)やFluent API設定が追加されます。ナビゲーションプロパティが定義されている場合、EF Coreは外部キーを自動生成します。

B2BUser

管理者ユーザーの最小限情報を保持。

public class B2BUser
{
    public int Id { get; set; }
    public string Subject { get; set; }           // UUID(EcAuth内部識別子)
    public string? ExternalId { get; set; }       // EC-CUBEのlogin_id等
    public string UserType { get; set; }          // "admin", "staff" 等
    public int OrganizationId { get; set; }       // マルチテナント
    public DateTimeOffset CreatedAt { get; set; }
    public DateTimeOffset UpdatedAt { get; set; }

    // Navigation properties
    public Organization Organization { get; set; }
    public ICollection<B2BPasskeyCredential> PasskeyCredentials { get; set; }
}

B2BPasskeyCredential

パスキー(公開鍵クレデンシャル)情報を保持。

public class B2BPasskeyCredential
{
    public int Id { get; set; }
    public string B2BSubject { get; set; }        // B2BUser.Subject
    public byte[] CredentialId { get; set; }      // WebAuthn credential ID
    public byte[] PublicKey { get; set; }         // COSE形式公開鍵
    public uint SignCount { get; set; }           // リプレイ攻撃防止カウンター
    public string? DeviceName { get; set; }       // "MacBook Pro", "iPhone" 等
    public Guid AaGuid { get; set; }              // Authenticator Attestation GUID
    public string[] Transports { get; set; }      // ["internal", "usb", "nfc", "ble"] 等
    public DateTimeOffset CreatedAt { get; set; }
    public DateTimeOffset? LastUsedAt { get; set; }

    // Navigation property
    public B2BUser B2BUser { get; set; }
}

WebAuthnChallenge

認証チャレンジの一時保存。

public class WebAuthnChallenge
{
    public int Id { get; set; }
    public string Challenge { get; set; }         // Base64URL形式
    public string SessionId { get; set; }         // セッション識別子
    public string Type { get; set; }              // "registration" or "authentication"
    public string UserType { get; set; }          // "b2b" or "b2c"
    public string? Subject { get; set; }          // ユーザーSubject
                                                 // - UserType == "b2b": 登録・認証ともに必須(既存ユーザーへのパスキー追加)
                                                 // - UserType == "b2c" && Type == "authentication": 必須
                                                 // - UserType == "b2c" && Type == "registration": null許容(JITプロビジョニング)
    public string? RpId { get; set; }             // EC-CUBEサイトのドメイン
    public int ClientId { get; set; }             // クライアントID
    public DateTimeOffset ExpiresAt { get; set; } // 5分で期限切れ
    public DateTimeOffset CreatedAt { get; set; }
}

ER図

erDiagram
    Organization ||--o{ B2BUser : has
    B2BUser ||--o{ B2BPasskeyCredential : has
    Client ||--o{ WebAuthnChallenge : has

APIエンドポイント設計

登録フロー

POST /v1/b2b/passkey/register/options

登録オプション(チャレンジ)を生成。

リクエスト:

{
  "client_id": "your_client_id",
  "rp_id": "shop.example.com",
  "b2b_subject": "uuid-of-admin-user",
  "device_name": "MacBook Pro"
}

レスポンス:

{
  "session_id": "random-session-id",
  "challenge": "base64url-encoded-challenge",
  "rp": {
    "id": "shop.example.com",
    "name": "ショップ名"
  },
  "user": {
    "id": "base64url-encoded-user-id",
    "name": "admin@shop.example.com",
    "displayName": "管理者"
  },
  "pubKeyCredParams": [
    { "type": "public-key", "alg": -7 },
    { "type": "public-key", "alg": -257 }
  ],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "residentKey": "preferred",
    "userVerification": "preferred"
  },
  "timeout": 60000,
  "attestation": "none"
}

POST /v1/b2b/passkey/register/verify

パスキー登録を完了。

リクエスト:

{
  "session_id": "random-session-id",
  "client_id": "your_client_id",
  "response": {
    "id": "credential-id",
    "rawId": "base64url-encoded-raw-id",
    "response": {
      "attestationObject": "base64url-encoded-attestation",
      "clientDataJSON": "base64url-encoded-client-data"
    },
    "type": "public-key"
  },
  "device_name": "MacBook Pro"
}

レスポンス:

{
  "success": true,
  "credential_id": "base64url-encoded-credential-id"
}

認証フロー

POST /v1/b2b/passkey/authenticate/options

認証オプション(チャレンジ)を生成。

リクエスト:

{
  "client_id": "your_client_id",
  "rp_id": "shop.example.com"
}

レスポンス:

{
  "session_id": "random-session-id",
  "challenge": "base64url-encoded-challenge",
  "rpId": "shop.example.com",
  "allowCredentials": [
    {
      "id": "base64url-encoded-credential-id",
      "type": "public-key",
      "transports": ["internal"]
    }
  ],
  "userVerification": "preferred",
  "timeout": 60000
}

POST /v1/b2b/passkey/authenticate/verify

パスキー認証を検証し、認可コードを発行。

リクエスト:

{
  "session_id": "random-session-id",
  "client_id": "your_client_id",
  "redirect_uri": "https://shop.example.com/admin/ecauth/callback",
  "state": "random-state",
  "response": {
    "id": "credential-id",
    "rawId": "base64url-encoded-raw-id",
    "response": {
      "authenticatorData": "base64url-encoded-auth-data",
      "clientDataJSON": "base64url-encoded-client-data",
      "signature": "base64url-encoded-signature"
    },
    "type": "public-key"
  }
}

レスポンス:

{
  "redirect_url": "https://shop.example.com/admin/ecauth/callback?code=xxx&state=xxx"
}

管理API

GET /v1/b2b/passkey/list

登録済みパスキー一覧を取得。

DELETE /v1/b2b/passkey/{credentialId}

パスキーを削除。

認証フロー詳細

パスキー登録フロー

sequenceDiagram
    participant Admin as 管理者
    participant ECCUBE as EC-CUBE
    participant EcAuth as EcAuth
    participant Auth as Authenticator

    Admin->>ECCUBE: ログイン済み
    Admin->>ECCUBE: 「パスキー追加」
    ECCUBE->>Admin: パスワード確認
    Admin->>ECCUBE: パスワード入力
    ECCUBE->>EcAuth: register/options
    EcAuth->>ECCUBE: challenge
    ECCUBE->>Auth: navigator.credentials.create()
    Auth-->>Auth: 生体認証/PIN
    Auth->>ECCUBE: credential
    ECCUBE->>EcAuth: register/verify
    EcAuth->>ECCUBE: success
    ECCUBE->>Admin: 登録完了

パスキー認証フロー

sequenceDiagram
    participant Admin as 管理者
    participant ECCUBE as EC-CUBE
    participant EcAuth as EcAuth
    participant Auth as Authenticator

    Admin->>ECCUBE: 管理画面アクセス
    Admin->>ECCUBE: 「パスキーでログイン」
    ECCUBE->>EcAuth: authenticate/options
    EcAuth->>ECCUBE: challenge + allowCredentials
    ECCUBE->>Auth: navigator.credentials.get()
    Auth-->>Auth: 生体認証/PIN
    Auth->>ECCUBE: assertion
    ECCUBE->>EcAuth: authenticate/verify
    EcAuth->>ECCUBE: redirect_url (code, state)
    ECCUBE->>Admin: redirect
    ECCUBE->>EcAuth: /v1/token (code)
    EcAuth->>ECCUBE: id_token, access_token
    ECCUBE->>Admin: ログイン完了

EC-CUBEプラグイン連携設計

RP ID設計

WebAuthnでは、RP IDはユーザーがアクセスしているドメインと一致する必要がある。

ユーザーがアクセス: https://shop.example.com/admin/
RP ID: shop.example.com
重要

navigator.credentials.create() および navigator.credentials.get() はEC-CUBEサイト上で実行する必要がある。

プラグイン実装範囲

機能 EC-CUBEプラグイン EcAuth
ログインUI -
パスキー管理画面 -
WebAuthn API呼び出し -
本人確認(パスワード) -
チャレンジ生成・検証 -
公開鍵保存・管理 -
署名検証 -
トークン発行 -

Clientテーブル拡張

パスキー用に許可されるRP ID(ドメイン)を管理。

public class Client
{
    // 既存フィールド...

    // パスキー用:許可されるRP ID(ドメイン)一覧
    public List<string> AllowedRpIds { get; set; } = new();
}

セキュリティ考慮事項

チャレンジ管理

  • 毎回ランダム生成(32バイト以上)
  • 5分で期限切れ
  • 使用後は即座に削除

SignCount検証

  • 認証ごとにカウンターを検証
  • カウンターが減少した場合はクローン検出として拒否

RP ID検証

  • クライアントに登録されたAllowedRpIdsと一致確認
  • フィッシングサイトでの認証を防止

本人確認(パスキー登録時)

  • EC-CUBEプラグイン側でパスワード再入力を要求
  • 検証成功後、一時トークン(5分有効)を発行
  • EcAuth APIはトークンを検証してから登録処理

登録完了通知

  • パスキー登録完了時にメール通知(推奨)
  • デバイス名、登録日時、IPアドレスを記載
  • 不正登録の早期発見

実装チェックリスト

Phase 1: 基盤実装

Phase 2: サービス層

Phase 3: APIエンドポイント

Phase 4: EC-CUBEプラグイン

Phase 5: テスト

参考資料