Next.js App RouterでmicroCMS Webhookの発行元を検証する

この記事では、Webhookを利用してCMSと外部サービスを連携する方法について説明されています。Webhook機能は最近のWebサービスやCMSで標準で提供されており、サービス同士の連携ワークフローを自動化することができます。microCMSでは、コンテンツごとにWebhookを設定することができ、APIで検証するためにシークレットキーを登録することもできます。また、Next.jsを使用してWebhookからのリクエストを検証する方法も説明されています。Webhookを利用してシステムとの連携を安全に行うために、シークレットの利用が重要です。

広告ここから
広告ここまで

目次

    この記事は、「CMS(WordPressやヘッドレスCMS) Advent Calendar 2023」12日目の記事です。

    WebhookでCMSと外部サービスを連携する

    最近のWebサービスやCMSには、Webhook機能が標準で提供されています。この機能を使うことで、例えば「記事の更新を反映させるために、SSGのビルドワークフローを開始する」ことや、「Algolia / Elasticsearch / OpensearchまたはベクトルDBなどに、検索用のインデックスを登録・更新する」ことといった、サービス同士の連携ワークフローを自動化できます。

    microCMSでは、コンテンツ(API)ごとにWebhookを設定する

    microCMSでは、コンテンツ(API)ごとにWebhookを設定できます。そのため、「このタイプのコンテンツは連携させたいが、他のタイプについてはその必要がない」などの判断が行えます。

    Webhookからのリクエストであることを、APIで検証する

    Webhookを利用したシステム連携では、「Webhookからのリクエスト以外のAPI呼び出しを、どのように無害化するか」が重要な要件としてあがります。これは、「Webhookから送信されるAPIリクエストと同じようなPOSTリクエストを、外部の第三者が直接送信してくる」などの攻撃を受けた際に、連携するサービスが意図しない動作を行わないようにするために行います。

    microCMSでは、Webhookごとにシークレットキーを登録できます。この設定はダッシュボードから行いましょう。

    設定したシークレットは、microCMSから送信されるWebhookのリクエストヘッダーに含まれます。

    microCMSから送信される検証用シークレットを、Next.jsで取得する

    ダッシュボードで設定したシークレットを利用して、「microCMSから送信されたリクエスト」であることを検証する処理を作りましょう。今回はHeadless CMSのフロントエンドで利用されることの多い、Next.jsを利用します。

    POSTリクエストを受け付けるPOST関数の中で、x-microcms-signatureヘッダーを取得しましょう。

    export async function POST(request: NextRequest) {
      return NextResponse.json({
        message: 'demo',
        header: request.headers.get('x-microcms-signature'),
      })
    }

    上のサンプルでは、取得したシークレットをそのままレスポンスで返します。下のcURLコマンドを実行すると、headerに送信したヘッダー情報が含まれたJSONがかえってきます。

    % curl -XPOST http://localhost:3000/api/webhook -H "X-MICROCMS-Signature:whsec_demo"

    このサンプルでは、次のようなレスポンスが取得できます。

    {
      "message":"demo",
      "header":"whsec_demo"
    }

    取得できたシークレットを利用して、検証処理を実装しましょう。

    環境変数から検証用のコードを取得し、リクエストを検証する

    ヘッダーからシークレットを取得する処理を実装しましたので、あとは検証用のコードを作ります。まずはmicroCMSで登録したシークレットキーを、Next.jsの環境変数に登録しましょう。

    MICROCMS_WEBHOOK_SECRET=whsec_demo

    環境変数に設定した値と、リクエストヘッダーで送信されてきた値が同じであることを、if文で検証します。一致しないか、どちらかの値が存在しなかった場合は、HTTP 401でエラーを返しましょう。

    export async function POST(request: NextRequest) {
      const microCMSWebhookSecret = process.env.MICROCMS_WEBHOOK_SECRET
      if (microCMSWebhookSecret && microCMSWebhookSecret !== request.headers.get('x-microcms-signature')) {
        return NextResponse.json(
          {
            message: 'Unauthorized',
          },
          {
            status: 401,
          },
        )
      }
      return NextResponse.json({
        message: 'ok',
      })
    }

    cURLで検証する

    最後に不正なAPIよびだしをブロックできているか試しましょう。下のcURLでは、不正なシークレットをヘッダーに設定して送信しているため、レスポンスがエラーになっています。

    % curl -XPOST http://localhost:3000/api/webhook -H "X-MICROCMS-Signature:INVALID_CODE"
    {"message":"Bad request."}

    正しいシークレットをヘッダーに設定すると、レスポンスが返ってきました。

    % curl -XPOST http://localhost:3000/api/webhook -H "X-MICROCMS-Signature:whsec_demo"
    {"message":"ok"}

    まとめ

    このように、比較的シンプルな設定と実装で、Webhook APIを保護することができます。Webhookを利用してシステムとmicroCMSを連携させる場合には、シークレットを利用して安全にAPI連携を行えるようにしましょう。

    参考

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark