> ## Documentation Index
> Fetch the complete documentation index at: https://docs.waffo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 認証

> API Key 認証で API リクエストを保護する

## 認証の概要

Waffo Pancake は API アクセスに 2 つの認証方式をサポートしています。

| 方式             | ユースケース       | 説明                                               |
| -------------- | ------------ | ------------------------------------------------ |
| **API Key**    | サーバー間通信      | RSA-SHA256 署名を使用した永続的な認証                         |
| **Store Slug** | 公開チェックアウトフロー | `X-Store-Slug` と `X-Environment` ヘッダーを使用した公開アクセス |

***

## API Key 認証

API Key は RSA-SHA256 署名を使用した永続的なサーバー間認証を提供します。秘密鍵がサーバーの外に出ることはありません。

### リクエストヘッダー

```bash theme={"system"}
X-Merchant-Id: MER_2aUyqjCzEIiEcYMKj7TZtw
X-Timestamp: 1705312200
X-Signature: BASE64_ENCODED_SIGNATURE
Content-Type: application/json
```

<Note>
  **API Key 認証では `X-Environment` ヘッダーは不要です。** 各 API Key は作成時に test または prod のいずれかに紐付けられます。環境は、署名の検証に成功した鍵によって自動的に決定されます。
</Note>

### SDK の使用（推奨）

```typescript theme={"system"}
import { WaffoPancake } from "@waffo/pancake-ts";

const client = new WaffoPancake({
  merchantId: process.env.WAFFO_MERCHANT_ID!,
  privateKey: process.env.WAFFO_PRIVATE_KEY!,
});

// All requests are automatically signed
const { store } = await client.stores.create({ name: "My Store" });
```

### 署名アルゴリズム（手動連携）

SDK を使用しない場合は、RSA-SHA256 リクエスト署名を実装する必要があります。

```
1. Build the canonical request:
   canonicalRequest = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + SHA256_BASE64(BODY)

2. Sign with RSA-SHA256:
   signature = RSA-SHA256(canonicalRequest, privateKey)

3. Base64 encode:
   X-Signature = Base64(signature)
```

### 手動署名の例

<CodeGroup>
  ```javascript Node.js theme={"system"}
  const crypto = require('crypto');

  const MERCHANT_ID = 'MER_2aUyqjCzEIiEcYMKj7TZtw';
  const PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY-----
  ...your private key...
  -----END RSA PRIVATE KEY-----`;

  async function callApiWithSignature(method, path, body) {
    const timestamp = Math.floor(Date.now() / 1000).toString();
    const bodyStr = JSON.stringify(body);
    const bodyHash = crypto.createHash('sha256').update(bodyStr).digest('base64');

    // Build canonical request
    const canonicalRequest = `${method}\n${path}\n${timestamp}\n${bodyHash}`;

    // RSA-SHA256 sign
    const signature = crypto.sign('sha256', Buffer.from(canonicalRequest), PRIVATE_KEY).toString('base64');

    const response = await fetch(`https://api.waffo.ai${path}`, {
      method,
      headers: {
        'Content-Type': 'application/json',
        'X-Merchant-Id': MERCHANT_ID,
        'X-Timestamp': timestamp,
        'X-Signature': signature
      },
      body: bodyStr
    });

    return response.json();
  }

  // Usage
  const result = await callApiWithSignature('POST', '/v1/actions/store/create-store', {
    name: 'My Store'
  });
  ```

  ```bash cURL theme={"system"}
  MERCHANT_ID="MER_2aUyqjCzEIiEcYMKj7TZtw"
  TIMESTAMP=$(date +%s)
  BODY='{"name":"My Store"}'
  BODY_HASH=$(echo -n "$BODY" | openssl dgst -sha256 -binary | base64 -w 0)
  CANONICAL_REQUEST="POST
  /v1/actions/store/create-store
  $TIMESTAMP
  $BODY_HASH"

  # Sign with openssl (requires private_key.pem file)
  SIGNATURE=$(echo -n "$CANONICAL_REQUEST" | openssl dgst -sha256 -sign private_key.pem | base64 -w 0)

  curl -X POST "https://api.waffo.ai/v1/actions/store/create-store" \
    -H "Content-Type: application/json" \
    -H "X-Merchant-Id: $MERCHANT_ID" \
    -H "X-Timestamp: $TIMESTAMP" \
    -H "X-Signature: $SIGNATURE" \
    -d "$BODY"
  ```

  ```python Python theme={"system"}
  import hashlib
  import time
  import base64
  import json
  import requests
  from cryptography.hazmat.primitives import hashes, serialization
  from cryptography.hazmat.primitives.asymmetric import padding

  MERCHANT_ID = 'MER_2aUyqjCzEIiEcYMKj7TZtw'
  PRIVATE_KEY = '''-----BEGIN RSA PRIVATE KEY-----
  ...your private key...
  -----END RSA PRIVATE KEY-----'''

  def call_api_with_signature(method, path, body):
      timestamp = str(int(time.time()))
      body_str = json.dumps(body, separators=(',', ':'))
      body_hash = base64.b64encode(hashlib.sha256(body_str.encode()).digest()).decode()

      # Build canonical request
      canonical_request = f"{method}\n{path}\n{timestamp}\n{body_hash}"

      # RSA-SHA256 sign
      private_key = serialization.load_pem_private_key(PRIVATE_KEY.encode(), password=None)
      signature = private_key.sign(canonical_request.encode(), padding.PKCS1v15(), hashes.SHA256())
      signature_b64 = base64.b64encode(signature).decode()

      response = requests.post(
          f'https://api.waffo.ai{path}',
          headers={
              'Content-Type': 'application/json',
              'X-Merchant-Id': MERCHANT_ID,
              'X-Timestamp': timestamp,
              'X-Signature': signature_b64
          },
          data=body_str
      )

      return response.json()

  # Usage
  result = call_api_with_signature('POST', '/v1/actions/store/create-store', {
      'name': 'My Store'
  })
  ```
</CodeGroup>

<Warning>
  **秘密鍵のセキュリティ**

  * クライアントサイドのコードで秘密鍵を公開しないでください
  * 秘密鍵をバージョン管理にコミットしないでください
  * 秘密鍵は環境変数に保存してください
  * 特にチームメンバーの変更後は、定期的に鍵をローテーションしてください
  * タイムスタンプはサーバー時刻から **5 分以内** である必要があります
</Warning>

***

## Store Slug 認証

公開チェックアウトフローでは、Store Slug 認証を使用します。これにより、訪問者は API Key 認証情報なしでチェックアウトセッションの作成や公開ストアデータのクエリが可能になります。

### リクエストヘッダー

```bash theme={"system"}
X-Store-Slug: my-awesome-store-k8x2m9ab
X-Environment: test | prod
Content-Type: application/json
```

### 使用例

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -X POST https://api.waffo.ai/v1/actions/checkout/create-session \
    -H "X-Store-Slug: my-awesome-store-k8x2m9ab" \
    -H "X-Environment: test" \
    -H "Content-Type: application/json" \
    -d '{"productId": "PROD_4cWAslE1GKkGeaOMl9Vbmy", "productType": "onetime", "currency": "USD"}'
  ```

  ```javascript JavaScript theme={"system"}
  const response = await fetch('https://api.waffo.ai/v1/graphql', {
    method: 'POST',
    headers: {
      'X-Store-Slug': 'my-awesome-store-k8x2m9ab',
      'Content-Type': 'application/json',
      'X-Environment': 'prod'
    },
    body: JSON.stringify({
      query: `query { store { id name onetimeProducts { id name prices } } }`
    })
  });
  ```
</CodeGroup>

***

## API Key の作成

<Steps>
  <Step title="API と開発ページに移動">
    ダッシュボード → API と開発 → API Keys に移動します
  </Step>

  <Step title="API Key を作成">
    「API Key を作成」をクリックして新しい RSA 鍵ペアを生成します。公開鍵は自動的にサーバーに送信されます。
  </Step>

  <Step title="名前と設定">
    分かりやすい名前（例：「Production Server」）を付け、対象環境（Test または Production）を選択します。
  </Step>

  <Step title="秘密鍵をダウンロード">
    **秘密鍵をすぐにダウンロードしてください。** 再度表示されることはありません。
  </Step>
</Steps>

<Warning>
  API Key の削除は即座に実行され、取り消しできません。削除された鍵で署名されたリクエストは `401 Unauthorized` で失敗します。
</Warning>

***

## 認証方式の比較

| 機能                   |  API Key |  Store Slug  |
| -------------------- | :------: | :----------: |
| サーバーサイドでの使用          |    Yes   |      No      |
| クライアントサイドでの使用        |    No    |      Yes     |
| X-Environment が必要    |    No    |      Yes     |
| Session Token の発行    |    Yes   |      No      |
| Checkout Session の作成 |    Yes   |      Yes     |
| 商品管理                 |    Yes   |      No      |
| GraphQL クエリ          |    Yes   | Yes（公開データのみ） |
| 有効期間                 | 永続（鍵で制御） |       -      |

***

## 認証エラー

| ステータス | エラー                      | 解決方法                                       |
| ----- | ------------------------ | ------------------------------------------ |
| 401   | Invalid signature        | 署名アルゴリズム、秘密鍵、タイムスタンプの鮮度（5 分ウィンドウ）を確認してください |
| 401   | Invalid or expired token | 再認証するか、有効な API Key を使用してください               |
| 403   | Insufficient permissions | そのロールがエンドポイントへのアクセス権を持っているか確認してください        |
| 400   | Missing authentication   | 必要なヘッダーが含まれていることを確認してください                  |

```json theme={"system"}
{
  "data": null,
  "errors": [
    {
      "message": "Invalid signature",
      "layer": "gateway"
    }
  ]
}
```

***

## セキュリティのベストプラクティス

1. クライアントサイドのコード、バージョン管理、公開リポジトリで**秘密鍵を公開しないでください**
2. すべての API リクエストに **HTTPS を使用**してください
3. 偽造リクエストを防ぐために **Webhook 署名を検証**してください
4. **テストと本番の鍵を分離**してください -- 環境ごとに異なる鍵を作成してください
5. 特にチームメンバーの変更後は、**定期的に鍵をローテーション**してください
6. ダッシュボードで **API 使用状況を監視**し、異常な活動がないか確認してください
