トピック

HMAC検証

HMAC検証は、FastOGがサブスクリプションベースのOG画像リクエストがすべて自社サーバーから送信されたものであることを確認する仕組みです。署名付きURLによりクレジットを保護します。サインアップ後、Dynamic APIプランを選択し、ダッシュボードでAPIキーとHMACシークレットを作成してください。有料APIレンダリングはクリーンです。このページでは、動的Open Graph画像のサーバーサイドのルールについて、正規クエリ署名から定数時間比較までを説明します。

概要

/api/v1/og エンドポイントに HMAC-SHA256 署名検証を追加し、API クォータの不正使用を防止します。これにより、正当なウェブサイトからのリクエストのみが OG 画像を生成できるようになります。

アーキテクチャ

2層認証:

  1. HMAC署名(第1層)— リクエストが信頼できる送信元(Webサイト)から発信されたことを検証します
  2. APIキー(第2層)— レンダリングクォータの計上用にユーザーアカウントを識別します

単一ミドルウェア(ApiKeyAuth): 署名(クエリパラメータ)を検証し、レンダリングクォータの計上用にAPIキーを解決します。これを1回の処理で行い、不正な署名は即座に失敗させます。

ヘッダーではなくクエリパラメータ: OG画像はソーシャルネットワークのクローラー(LinkedIn、Twitter、Facebook)によって取得され、これらのクローラーはカスタムヘッダーを送信しないため、署名はURLのクエリパラメータで渡されます。

GET /api/v1/og?title=Hello&key=sk_quick_123abc&s=<hmac>

実装

作成・変更したファイル

  • app/Http/Middleware/HmacSignature.php - HMAC署名(クエリパラメータ)を検証する新しいミドルウェア
  • routes/api.php - OGルートにHMACミドルウェアを追加
  • bootstrap/app.php - hmacミドルウェアのエイリアスを登録
  • app/Models/ApiKey.php - hmac_secretフィールド(ユーザーごとのシークレット)を追加
  • app/Http/Controllers/DashboardController.php - APIキー作成時にHMACシークレットを生成
  • database/migrations/2026_08_03_214931_add_hmac_secret_to_api_keys_table.php - hmac_secretカラム用のマイグレーション
  • resources/js/Pages/Dashboard/ApiKeys.svelte - HMACシークレットをユーザーに表示(一度のみ)
  • docs/hmac-signature-guide.md - クライアント実装ガイド
  • tests/Feature/HmacSignatureTest.php - 8件のテストが成功

署名アルゴリズム

method = HTTP method (e.g., "GET")
path  = request path (e.g., "/api/v1/og")
query = the request's DECODED query params

canonical = every param except `s`, keys sorted (array values sorted too),
            each `key=value` RFC-3986 encoded (unreserved-only), joined with "&"
message   = method + "/" + path + "\n" + canonical
signature = HMAC-SHA256(message, hmac_secret)

Query parameters:
  key=<apiKey>
  s=<signature>
  (no timestamp)

keyは正規クエリの一部であるため、署名はAPIキーに紐づいています。パラメータ(タイトル、テンプレート、画像など)またはキー自体を変更すると、正規化文字列が変わり、署名が不一致となり、401が返ってレンダリングもクォータ消費も行われません。パラメータが同一なら署名も同一となり、URLも同一になるため、エッジで無期限にキャッシュ可能で、リプレイしてもクォータは消費されません。

セキュリティ機能

  • コンテンツアドレス型署名 - パラメータを変更すると401が返り、レンダリングもクォータ消費も発生しません(URL自体が証明となります)
  • タイムスタンプなし - URLは公開HTMLに埋め込まれ、無期限にキャッシュされるため、5分を超えて有効である必要があります
  • 定数時間比較(hash_equals()) - タイミング攻撃を防止
  • メッセージにAPIキーを含める - 署名はキーに紐づいており、キーを交換すると失敗します
  • パス固有 - 署名は正確なパスに対してのみ有効
  • メソッド固有 - GET用の署名はPOSTでは機能しません
  • ユーザーごとのシークレット - 各APIキーに独自のHMACシークレットがあり、個別に失効させることができます(ローテーション=漏洩したURLのキルスイッチ)

設定

シークレットの生成(自動)

HMACシークレットは、ダッシュボードでAPIキーを作成する際に自動的に生成されます。APIキーと一緒に一度だけ表示され、その後は再び表示されることはありません。

手動生成(必要な場合):

bash
openssl rand -hex 32

データベーススキーマ

sql
ALTER TABLE api_keys ADD COLUMN hmac_secret VARCHAR(64)
  COMMENT 'HMAC-SHA256 secret for signing OG image URLs (hex, 64 chars)';

環境変数

グローバルな環境変数は不要です。シークレットはapi_keysテーブルにユーザーごとに保存されます。

使用例(ウェブサイトのバックエンド)

javascript
// Node.js example - generate signed OG image URL
const crypto = require("crypto");

// RFC-3986 encode (encodeURIComponent leaves !'()* unencoded).
const enc = (s) =>
    encodeURIComponent(s).replace(
        /[!'()*]/g,
        (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase(),
    );

function generateOgImageUrl(baseUrl, params, apiKey, hmacSecret) {
    const all = { ...params, key: apiKey };

    // Canonical: every param (except s), sorted, RFC-3986 encoded, joined with "&".
    const pairs = [];
    for (const [k, v] of Object.entries(all)) {
        for (const vv of Array.isArray(v) ? [...v].sort() : [v])
            pairs.push(`${enc(k)}=${enc(vv)}`);
    }
    pairs.sort();
    const canonical = pairs.join("&");

    const message = `GET/api/v1/og\n${canonical}`;
    const signature = crypto
        .createHmac("sha256", hmacSecret)
        .update(message)
        .digest("hex");

    const url = new URL(baseUrl);
    for (const [k, v] of Object.entries(all)) {
        const isArray = Array.isArray(v);
        for (const vv of isArray ? v : [v])
            url.searchParams.append(isArray ? `${k}[]` : k, vv);
    }
    url.searchParams.set("s", signature);
    return url.toString();
}

// Usage in meta tag
const ogUrl = generateOgImageUrl(
    "https://fastog.com/api/v1/og",
    { title: "Como criar um SaaS", template: "blog" },
    "sk_quick_123abc",
    process.env.HMAC_SECRET,
);

// <meta property="og:image" content="{ogUrl}" />

エラーレスポンス

ステータス コード 説明
401 missing_signature key または s クエリパラメータがありません
401 invalid_signature シグネチャが正規クエリと一致しません(改ざんまたはシークレットの誤り)

テスト

テストを実行します:

bash
php artisan test --filter HmacSignatureTest

8件のテストがすべて成功します:

  • ✅ 署名なしのリクエストは拒否される
  • ✅ キーパラメータが欠落したリクエストは拒否される
  • ✅ 改ざんされたコンテンツパラメータ(タイトル変更)は拒否される
  • ✅ 追加されたパラメータは拒否される
  • ✅ 無効な署名は拒否される
  • ✅ 有効な署名はHMACチェックを通過する
  • ✅ 署名はパス固有である
  • ✅ 署名はメソッド固有である
  • ✅ APIキーが異なるとシークレットも異なる

パフォーマンスへの影響

  • 無視できる程度 - HMAC-SHA256 の計算はリクエストあたり約 0.1ms です
  • 追加のネットワーク呼び出しなし - 検証はローカルで行われます
  • 迅速に失敗 - 無効な署名は API キーの検索前に拒否されます

セキュリティに関する考慮事項

  1. クライアントサイドのコードでシークレットを公開しない — 署名はサーバー側でのみ行う
  2. HTTPSを使用する — HMACは不正なリクエストを防ぎ、HTTPSは中間者攻撃(MITM)を防ぐ
  3. シークレットを定期的にローテーションする — 特に漏えいした場合(APIキーの再生成が必要)
  4. 署名の失敗を監視する — 繰り返しの失敗は攻撃の兆候である可能性がある
  5. ユーザーごとの分離 — 各APIキーには独自のHMACシークレットがあり、1つの漏えいが他に影響しない

参考資料

完全なAPIリファレンス

無料で試す — サインアップ不要

動的OG画像を数秒でプレビューおよびテスト。

無料のOG画像テスターを開く

A BeCaSimulations site