# 機能設計書 5-OAuth認証ライブラリ

## 概要

本ドキュメントは、Decap CMSのOAuth認証ライブラリ（decap-cms-lib-auth）の設計を記載する。Implicit OAuth、PKCE OAuth、Netlify Authの3種類の認証フローを提供する共有ライブラリである。

### 本機能の処理概要

本機能は、Decap CMSの各バックエンドが使用する認証フローの実装を共有ライブラリとして提供する。3つの認証クラス（ImplicitAuthenticator、PkceAuthenticator、NetlifyAuthenticator）があり、それぞれ異なるOAuth認証プロトコルを実装している。

**業務上の目的・背景**：Decap CMSは複数のGitホスティングサービス（GitHub, GitLab, Bitbucket等）をバックエンドとして使用するが、各サービスが異なるOAuth認証方式をサポートしている。この共有ライブラリにより、認証ロジックの重複を排除し、新しいバックエンドの追加時にも既存の認証フローを再利用できる。

**機能の利用シーン**：ユーザーがCMSにログインする際、設定されたバックエンドに応じて適切な認証フローが選択・実行される。Implicit OAuthはAzure DevOps等で使用、PKCE OAuthはセキュアな認証が必要な場合に使用、Netlify AuthはGit Gatewayバックエンド使用時に利用される。

**主要な処理内容**：
1. ImplicitAuthenticator: Implicit Grant Flowによるトークン取得（URLフラグメントからアクセストークンを抽出）
2. PkceAuthenticator: Authorization Code Flow with PKCEによる安全なトークン取得（code_verifier/code_challengeを使用）
3. NetlifyAuthenticator: Netlifyの認証エンドポイントを経由したポップアップウィンドウベースの認証
4. 共有ユーティリティ: nonce生成・検証、プロトコルセキュリティチェック

**関連システム・外部連携**：OAuth 2.0プロバイダー（GitHub, GitLab, Bitbucket等）、Netlify API、OIDC対応プロバイダー。

**権限による制御**：OAuthスコープ（scope）パラメータにより、取得するアクセス権限の範囲を制御する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | ログイン画面 | 補助機能 | Implicit OAuth・Netlify Auth等の認証フローライブラリを利用してバックエンドとの認証処理を実行 |
| 2 | PKCE認証画面 | 補助機能 | PkceAuthenticatorクラスを使用してPKCEフローの初期化・コールバック処理・トークン取得を実行 |

## 機能種別

認証処理 / ライブラリ

## 入力仕様

### 入力パラメータ

#### ImplicitAuthenticator

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| config.base_url | string | Yes | OAuth認証エンドポイントのベースURL | - |
| config.auth_endpoint | string | Yes | 認証エンドポイントパス | - |
| config.app_id | string | Yes | OAuthアプリケーションID | - |
| options.scope | string | Yes | OAuthスコープ | - |
| options.prompt | string | No | プロンプトパラメータ | - |
| options.resource | string | No | リソースパラメータ | - |

#### PkceAuthenticator

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| config.base_url | string | Yes | OAuth認証エンドポイントのベースURL | - |
| config.auth_endpoint | string | No | 認証エンドポイントパス | - |
| config.auth_token_endpoint | string | No | トークンエンドポイントパス | - |
| config.auth_token_endpoint_content_type | string | No | トークンリクエストのContent-Type | - |
| config.app_id | string | Yes | OAuthアプリケーションID | - |
| config.use_oidc | boolean | No | OIDC自動検出の使用 | - |

#### NetlifyAuthenticator

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| config.site_id | string | No | NetlifyサイトID | - |
| config.base_url | string | No | Netlify APIベースURL（デフォルト: https://api.netlify.com） | - |
| config.auth_endpoint | string | No | 認証エンドポイント（デフォルト: auth） | - |
| options.provider | string | Yes | 認証プロバイダー（github, gitlab, bitbucket, email） | - |

### 入力データソース

- CMS設定ファイル（config.yml）のbackendセクション
- OAuthリダイレクトコールバック（URLハッシュまたはクエリパラメータ）
- セッションストレージ（nonce, code_verifier）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| token | string | OAuthアクセストークン |
| data | object | バックエンド固有の追加データ（refresh_token等） |
| error | Error | 認証エラー情報 |

### 出力先

- コールバック関数（cb）経由で呼び出し元に返却

## 処理フロー

### 処理シーケンス

```
[ImplicitAuthenticator]
1. authenticate(options, cb)
   └─ プロトコルセキュリティチェック（HTTPS必須）
   └─ 認証URLの構築（client_id, redirect_uri, response_type=token, scope, state）
   └─ nonce生成・sessionStorageに保存
   └─ document.location.assignでリダイレクト
2. completeAuth(cb)
   └─ URLハッシュからアクセストークンを抽出
   └─ clearHash()でハッシュをクリア
   └─ nonce検証
   └─ エラーチェック → cb(error) / cb(null, {token, ...data})

[PkceAuthenticator]
1. authenticate(options, cb)
   └─ プロトコルセキュリティチェック
   └─ OIDC設定の自動読み込み（use_oidc時）
   └─ code_verifier生成・sessionStorageに保存
   └─ code_challenge生成（SHA-256 + Base64URL）
   └─ 認証URLの構築（response_type=code, code_challenge等）
   └─ document.location.assignでリダイレクト
2. completeAuth(cb)
   └─ URLクエリパラメータからcodeを抽出
   └─ nonce検証
   └─ token_endpoint にPOSTリクエスト（code + code_verifier）
   └─ code_verifierクリア
   └─ cb(null, {token, ...data})

[NetlifyAuthenticator]
1. authenticate(options, cb)
   └─ プロバイダーとサイトID検証
   └─ ポップアップウィンドウで認証ページを開く
   └─ postMessage APIでhandshake
   └─ 認証結果のpostMessage受信 → cb(null, data) / cb(error)
2. refresh(options, cb)
   └─ refresh_tokenを使用してトークンリフレッシュ
```

### フローチャート

```mermaid
flowchart TD
    A[認証開始] --> B{認証方式}
    B -->|Implicit| C[認証URLへリダイレクト]
    B -->|PKCE| D[code_verifier生成]
    B -->|Netlify| E[ポップアップ認証]
    C --> F[プロバイダー認証画面]
    D --> G[code_challenge生成]
    G --> F
    F --> H{コールバック}
    H -->|Implicit| I[URLハッシュからトークン抽出]
    H -->|PKCE| J[codeからトークン交換]
    E --> K[postMessage APIで結果受信]
    I --> L[nonce検証]
    J --> L
    K --> M[認証データ返却]
    L --> M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | HTTPSプロトコル必須 | HTTP（非暗号化）プロトコルでは認証を拒否する（localhost/127.0.0.1は例外） | Implicit/PKCE認証時 |
| BR-02 | nonce検証 | CSRF攻撃防止のため、認証リクエストと応答のnonceが一致することを検証 | Implicit/PKCE completeAuth時 |
| BR-03 | PKCE code_verifier | PKCE認証ではcode_verifierをsessionStorageに保存し、トークン交換時に送信 | PKCE認証時 |
| BR-04 | OIDC自動検出 | use_oidc=trueの場合、.well-known/openid-configurationからエンドポイントを自動検出 | PkceAuthenticator初期化時 |
| BR-05 | Netlifyプロバイダー制限 | github, gitlab, bitbucket, emailの4プロバイダーに対応 | Netlify認証時 |
| BR-06 | サイトID自動取得 | site_idが未設定の場合、localhostでは'demo.decapcms.org'、それ以外ではホスト名を使用 | Netlify認証時 |

### 計算ロジック

- code_challenge = Base64URL(SHA-256(code_verifier)) から「=」パディングを除去し、「+」を「-」に、「/」を「_」に変換

## データベース操作仕様

本機能はクライアントサイドライブラリのため、データベース操作はない。sessionStorageのみ使用。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | プロトコルエラー | HTTPS以外（localhost除く）でアクセス | 'Cannot authenticate over insecure protocol!'エラー |
| - | nonce不一致 | nonceが保存値と一致しない | 'Invalid nonce'エラー |
| - | OAuthエラー | プロバイダーからエラーレスポンス | error + error_descriptionをエラーメッセージに含める |
| - | OIDC設定エラー | .well-known/openid-configurationの取得/パース失敗 | 'Failed to load OIDC configuration'等のエラー |
| - | プロバイダー未指定 | Netlify認証でproviderが未指定 | 'You must specify a provider'エラー |
| - | サイトID未設定 | localhost以外でsite_id未設定 | 'You must set a site_id'エラー |

### リトライ仕様

認証フローのリトライ機構は実装されていない。

## トランザクション仕様

該当なし。

## パフォーマンス要件

- OIDC自動検出はネットワークリクエスト1回で完了すること
- PKCE code_challenge生成はWeb Crypto APIを使用し、クライアント側で高速に実行されること

## セキュリティ考慮事項

- PKCE認証はImplicit認証よりセキュアであり、推奨される認証方式
- code_verifierは128バイトのランダム文字列で、sessionStorageに保存
- nonceはUUID v4で生成され、CSRF攻撃を防止
- Implicit認証ではアクセストークンがURLフラグメントに含まれるため、clearHash()で即座にURLからトークンを削除
- Netlify認証ではpostMessage APIのoriginチェックが行われる

## 備考

- code_verifier生成に使用される文字セット: `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.`（`_~`は均等分布のため除外）
- nonce管理: sessionStorageキー `decap-cms-auth`
- code_verifier管理: sessionStorageキー `decap-cms-pkce-verifier-code`
- Netlify認証のポップアップウィンドウサイズ: github/gitlab=960x600, bitbucket=960x500, email=500x400

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | utils.js | `packages/decap-cms-lib-auth/src/utils.js` | 共有ユーティリティ（nonce, プロトコルチェック） |

**読解のコツ**: 3つの認証クラスはすべてコールバックパターン（cb(error, data)）で結果を返す。Promise化されていない点に注意。

#### Step 2: Implicit OAuth認証を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | implicit-oauth.js | `packages/decap-cms-lib-auth/src/implicit-oauth.js` | ImplicitAuthenticatorクラス |

**主要処理フロー**:
- **8-14行目**: コンストラクタ - auth_url構築、appID、clearHash保存
- **16-40行目**: authenticate() - 認証URLパラメータ構築、nonceをstateに埋め込み、リダイレクト
- **45-69行目**: completeAuth() - URLハッシュからトークン抽出、nonce検証

#### Step 3: PKCE OAuth認証を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | pkce-oauth.js | `packages/decap-cms-lib-auth/src/pkce-oauth.js` | PkceAuthenticatorクラス |

**主要処理フロー**:
- **6-12行目**: sha256() - Web Crypto APIによるSHA-256ハッシュ
- **15-25行目**: generateVerifierCode() - 128バイトランダム文字列生成
- **27-31行目**: createCodeChallenge() - Base64URL変換
- **33-47行目**: code_verifier管理関数群（create, get, clear）
- **63-77行目**: コンストラクタ - OIDC対応のURL設定
- **79-97行目**: _loadOidcConfig() - .well-known/openid-configuration取得
- **99-125行目**: authenticate() - code_challenge付きの認証URLリダイレクト
- **130-189行目**: completeAuth() - codeからトークン交換

#### Step 4: Netlify認証を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | netlify-auth.js | `packages/decap-cms-lib-auth/src/netlify-auth.js` | Authenticatorクラス（NetlifyAuthenticator） |

**主要処理フロー**:
- **16-33行目**: PROVIDERS定数 - プロバイダーごとのポップアップサイズ
- **42-51行目**: handshakeCallback() - postMessageによるハンドシェイク
- **53-77行目**: authorizeCallback() - 認証結果のpostMessage受信
- **87-129行目**: authenticate() - ポップアップウィンドウでの認証
- **132-162行目**: refresh() - リフレッシュトークンによるトークン更新

### プログラム呼び出し階層図

```
[ImplicitAuthenticator]
authenticate(options, cb)
    +-- isInsecureProtocol()
    +-- createNonce()
    +-- document.location.assign(authURL)

completeAuth(cb)
    +-- this.clearHash()
    +-- validateNonce(nonce)
    +-- cb(null, {token, ...data})

[PkceAuthenticator]
authenticate(options, cb)
    +-- isInsecureProtocol()
    +-- _loadOidcConfig()
    +-- createCodeVerifier()
    |       +-- generateVerifierCode()
    +-- createCodeChallenge(codeVerifier)
    |       +-- sha256(text)
    +-- createNonce()
    +-- document.location.assign(authURL)

completeAuth(cb)
    +-- validateNonce(nonce)
    +-- _loadOidcConfig()
    +-- getCodeVerifier()
    +-- fetch(authURL, {method: 'POST'})
    +-- clearCodeVerifier()
    +-- cb(null, {token, ...data})

[NetlifyAuthenticator]
authenticate(options, cb)
    +-- getSiteID()
    +-- window.addEventListener('message', handshakeCallback)
    +-- window.open(url)
    +-- handshakeCallback -> authorizeCallback -> cb
```

### データフロー図

```
[入力]                      [処理]                           [出力]

config (base_url,        ImplicitAuthenticator
app_id) -----------------> authenticate() -----> プロバイダーへリダイレクト
                                                        |
URLフラグメント             completeAuth() <----------+
(#access_token) ---------> nonce検証 -------> cb(null, {token})

config (base_url,        PkceAuthenticator
app_id) -----------------> authenticate() -----> プロバイダーへリダイレクト
                           code_verifier生成           |
URLクエリ                  completeAuth() <-----------+
(?code=xxx) -------------> code+verifier -----> トークンエンドポイント
                                                  cb(null, {token})

config (site_id) ------> NetlifyAuthenticator
                          authenticate() -------> ポップアップウィンドウ
                          postMessage受信 <------- 認証結果
                                                  cb(null, data)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.js | `packages/decap-cms-lib-auth/src/index.js` | ソース | ライブラリエクスポート |
| implicit-oauth.js | `packages/decap-cms-lib-auth/src/implicit-oauth.js` | ソース | Implicit OAuth認証 |
| pkce-oauth.js | `packages/decap-cms-lib-auth/src/pkce-oauth.js` | ソース | PKCE OAuth認証 |
| netlify-auth.js | `packages/decap-cms-lib-auth/src/netlify-auth.js` | ソース | Netlify認証 |
| utils.js | `packages/decap-cms-lib-auth/src/utils.js` | ソース | 共有ユーティリティ（nonce, プロトコルチェック） |
