FlawOpen

flawopen.com/リファレンス/Next.js Server Actions CSRF

Next.js Server Actions の CSRF:Origin 検証と安全な設定

CWE-352: Cross-Site Request Forgery (CSRF)フレームワークセキュリティガイド
要点

Next.js Server Actions は POST を使い、Origin のホストを x-forwarded-host または host と比較します。訪問者の招待状に書かれた場所が、サーバーの想定と一致するか確かめるようなものです。これはクロスサイト送信の防止に役立ちますが、認可ではありません。各アクションでユーザー認証、入力検証、権限確認が必要です。

重要な制限があります。現行ドキュメントでは Origin のないリクエストは警告付きで処理されます。リバースプロキシも比較対象のホストに影響します。この検証を防御層の一つと捉え、プロキシで信頼できるホスト値を設定してください。

Origin
ブラウザーが送る開始ページのオリジン。Next.js はそのホストとアプリのホストを比較します。
Host / X-Forwarded-Host
比較に使われるホスト値。プロキシは転送情報を設定または検証します。
allowedOrigins
Server Actions が受け入れる追加 Origin ホストの明示リスト。正確なホストとワイルドカードが使えます。
Authorization
認証済みユーザーが特定オブジェクトに対してこの操作を実行できるかを判定します。

Server Actions の組み込み CSRF 検証の流れ

1

ブラウザーが Server Action を送信

Server Action は HTTP POST で呼び出されます。SameSite cookie は多くのクロスサイト送信を既定で防ぎますが、POST だけで完全な CSRF 防御にはなりません。

2

Next.js がホストを比較

Origin がある場合、Next.js はそのホストを x-forwarded-host または host と比較します。一致せず、serverActions.allowedOrigins にも含まれなければ拒否されます。

3

アクションは独自のポリシーを適用

リクエスト検証後も、アクションはユーザーを認証し、各引数を検証し、操作を認可します。アクション ID や非表示フォーム項目はアクセス制御ではありません。

重要な制約:Origin 欠落と allowedOrigins

ドキュメント上、Origin の欠落は既定で拒否されない

現行ドキュメントでは、Origin のないリクエストは警告付きで続行します。すべてのリクエストが比較済みだと考えず、欠落の理由を調べ、アクション内で認証、認可、入力検証を行ってください。

ワイルドカードは信頼する Origin の範囲を広げる

allowedOrigins は正確なホストとワイルドカードを受け付けます。* はラベル一つ、** は一つ以上に一致します。組織が管理し、必要とするドメインとサブドメインだけを許可してください。ユーザー管理のサブドメインまで含むと、管理外の Origin を信頼することになります。

リバースプロキシもセキュリティ境界として設定

Next.js は比較に x-forwarded-host または host を使います。エッジプロキシはクライアント由来の転送ヘッダーを破棄し、検証済みの公開ホスト名で値を設定します。アプリには信頼するプロキシ経由だけでアクセスできるようにします。プロキシが内部ホスト名を転送する場合は、必要な公開 Origin だけを allowedOrigins に設定します。任意のホストを信頼して不一致を解消しないでください。

信頼範囲が広すぎる
// Every subdomain, including delegated or tenant-controlled names, is trusted.
module.exports = {
  experimental: {
    serverActions: {
      allowedOrigins: ['**.example.com'],
    },
  },
};
信頼範囲を限定
// Add only the public host that this deployment requires.
module.exports = {
  experimental: {
    serverActions: {
      allowedOrigins: ['admin.example.com'],
    },
  },
};

// Configure the edge to overwrite X-Forwarded-Host
// with the validated public hostname.

Host に関する勧告:関連するが CSRF とは別のリスク

Host への信頼は Server Actions で SSRF の原因になった事例があります。CVE-2024-34351 は、Host を変更でき、アクションが / で始まる相対リダイレクトを行う条件下で、14.1.1 より前のセルフホスト版に影響しました。別の2026年7月の Vercel 勧告は、カスタムサーバーでクライアントがホスト関連ヘッダーを制御できる場合の SSRF と内部値の漏えいを説明し、修正版として 15.5.21 と 16.2.11 を挙げています。これはホスト信頼の欠陥であり、Origin による CSRF 検証の回避を示すものではありません。

2026年の勧告に対しては修正版へ更新してください。それまでの間、エッジまたはプロキシで Host と X-Forwarded-Host を固定または検証します。勧告では 14.2.0 以降の追加策として __NEXT_PRIVATE_ORIGIN も説明されています。適用前に勧告で対象・サポート中のリリース系列を確認してください。

すべての Server Action を認可・検証

Next.js の文書は、アクション引数を信頼できない値として扱い、現在のユーザーが呼び出せるか確認するよう求めています。危険な例は呼び出し元が渡したオブジェクト ID を信頼します。修正版は認証と ID 検証を行い、データベース更新自体に所有者条件を含めます。

オブジェクト認可なし
"use server";

export async function deleteInvoice(invoiceId: string) {
  await db.invoice.delete({ where: { id: invoiceId } });
}
認証・検証・認可
"use server";

import { z } from "zod";
import { requireUser } from "@/auth";

const invoiceIdSchema = z.string().uuid();

export async function deleteInvoice(rawId: unknown) {
  const user = await requireUser();
  const invoiceId = invoiceIdSchema.parse(rawId);
  const result = await db.invoice.deleteMany({
    where: { id: invoiceId, ownerId: user.id },
  });
  if (result.count !== 1) throw new Error("Forbidden");
}

Server Actions の CSRF と認可チェックリスト

参考資料

関連項目 クロスサイトリクエストフォージェリ(CSRF、CWE-352):仕組みと防ぎ方 — flawopen.com SameSite=Strict は CSRF トークンの代わりになりますか? GET に CSRF トークンは必要? localStorage の JWT と HttpOnly クッキー:どちらが安全か — flawopen.com