# Next.js Server ActionsのCSRF / Origin対策 — 何が標準で守られ、何を足すべきか

> Next.js App RouterのServer ActionsはPOST限定＋Origin/Host一致チェック＋暗号化されたアクションIDで一定のCSRF耐性を持つ。しかし十分ではない。Origin検証・SameSite Cookie・Route Handlerの保護を、何が自動で守られ何を足すか、公式が示す範囲に忠実に正直解説します。

- 公開日: 2026-06-28
- 著者: 友田 陽大
- タグ: Next.js, セキュリティ, TypeScript, アーキテクチャ設計
- URL: https://tomodahinata.com/blog/nextjs-csrf-origin-protection-server-actions-guide
- カテゴリ: アプリ層セキュリティ
- 総合ガイド: https://tomodahinata.com/blog/nextjs-supabase-application-security-guide

## 要点

- 結論：Next.jsのServer Actionsは『POSTのみで起動』『OriginヘッダーとHost(またはX-Forwarded-Host)の一致チェック』『推測困難な暗号化アクションID』を標準で持つ。これは有用なCSRF耐性だが、万能ではない
- 正直に言うと、標準保護はServer Actionsという1つの経路にしか効かない。Route Handler（route.ts）のGET/POSTや、Cookieだけで認証する自前APIは、あなたが明示的に守らない限り保護されない
- 足すべき防御は3つ——SameSite=Lax/Strict Cookie、状態変更経路でのOrigin/Host検証、リバースプロキシ環境のallowedOrigins設定。Cookie以外の経路で認証するならCSRFの主因が消える
- 標準のOrigin/Host照合はブラウザがOriginを正しく送る前提に立つ。古いブラウザや非ブラウザ経路、サブドメイン跨ぎのCookie共有では前提が崩れる。だから多層で守る
- 公式自身が『認証層が無い場合のリスクを下げるだけ。Server Actionsは直POSTで到達可能として扱い、各アクション内で認証・認可を検証せよ』と述べている。CSRF対策は認可の代わりにならない

---

最初に結論を述べます。**Next.js（App Router）の Server Actions は、標準で「POSTでしか起動できない」「Origin ヘッダーと Host ヘッダーの一致を確認する」「推測困難な暗号化アクションIDを使う」という、実用的なCSRF耐性を備えています。** これは確かに有用で、何も設定しなくてもよくあるCSRFの大半を弾きます。しかし——**万能ではありません。** 標準保護が効くのは「Server Actions」という1つの経路だけで、`route.ts` の Route Handler や、Cookie だけで認証する自前のAPIは、あなたが明示的に守らない限りCSRFから保護されません。

本記事は、この境界を正確に引き直します。「Next.js が標準で守ってくれる範囲」を**公式ドキュメントが示す範囲に忠実に**述べ、そのうえで**開発者が自分で足すべき防御**——SameSite Cookie、状態変更経路での Origin/Host 検証、リバースプロキシ環境の `allowedOrigins`、必要なら double-submit トークン——を、脆弱なコードと修正コードで示します。「Next.js のCSRF対策は弱い」という話でも「もう何もしなくていい」という話でもありません。**標準で何が守られるかを正確に知り、残った穴だけを最小コストで塞ぐ**ための地図です。

---

## 1. そもそもCSRFとは——「被害者のCookieで、意図しない状態変更」

まず用語を正確に押さえます。CSRF（Cross-Site Request Forgery、クロスサイトリクエストフォージェリ）は、**ログイン済みの被害者のブラウザに、攻撃者が用意した別サイトから、本人の意図しないリクエストを送らせる**攻撃です。OWASP Top 10 でも長く扱われてきた古典的な脅威クラスです（[OWASP Top 10](https://owasp.org/www-project-top-ten/)）。

仕組みは拍子抜けするほど単純です。

```text
1. 被害者が bank.example にログイン中（セッションCookieがブラウザに保存されている）
2. 被害者が攻撃者の罠サイト evil.example を開く
3. 罠サイトに仕込まれた <form action="https://bank.example/transfer" method="POST"> が自動送信される
4. ブラウザは bank.example 宛のリクエストに、保存済みのセッションCookieを自動添付する
5. サーバーは「正規ユーザーのリクエスト」として送金処理を実行してしまう
```

CSRFの本質は2点です。第一に、**攻撃者はCookieの中身を読めません。** ブラウザが「同じドメイン宛なら自動でCookieを送る」性質を悪用するだけです。第二に、だからこそ**狙われるのは状態変更（書き込み）**です。読み取った結果は攻撃者に届かないので、CSRFの標的は送金・パスワード変更・削除といった「副作用のある操作」に限られます。

ここから2つの設計原則が導かれます。**(a) 状態変更は安全なメソッド（GET）で行わない**、**(b) 状態変更リクエストが「本当に自サイト由来か」を確認する。** Next.js の Server Actions の標準保護は、まさにこの2点を（部分的に）肩代わりするものです。次節で、その「肩代わりの範囲」を公式の記述どおりに正確に見ます。

---

## 2. Next.js Server Actions が標準で持つ保護——公式が示す範囲で

ここは本記事で最も誤解の多い箇所なので、**Next.js 公式ドキュメントが述べている範囲に厳密に沿って**整理します（[Next.js docs](https://nextjs.org/docs)）。憶測で「だから安全」と広げないことが重要です。

公式が「Server Actions の組み込みセキュリティ機能」として挙げているのは、次の3点です。

| 標準保護 | 公式の説明（要旨） | CSRFにどう効くか |
|---|---|---|
| **POST メソッド限定** | Server Actions は内部的に `POST` を使い、この HTTP メソッドでのみ起動できる | `<img>`/`<a>`/GET フォーム等のGETベースのCSRFを構造的に防ぐ |
| **Origin と Host の一致チェック** | Origin ヘッダーを Host ヘッダー（または `X-Forwarded-Host`）と比較し、一致しなければリクエストを中断する | 別オリジンの罠サイトからの送信を弾く |
| **暗号化アクションID** | クライアントがアクションを参照・呼び出すための、暗号化された非決定的なIDを生成。ビルド間で再計算される（最大14日キャッシュ） | エンドポイントのパスを推測されにくくする |

公式はこれに加えて、**「Server Actions は `<form>` から起動できるためCSRFにさらされうるが、`POST` 限定であること、そして現代ブラウザで `SameSite` Cookie がデフォルトであることが、ほとんどのCSRF脆弱性を防ぐ」**と明記しています。さらに**追加の保護**として、上記の Origin/Host 照合があり、**「一致しなければリクエストは中断される。つまり Server Actions はそれをホストするページと同じホストからしか起動できない」**と述べています。

ここまでが「公式が示す範囲」です。3つだけ、正確さのために補足します。

1. **暗号化アクションIDは「秘匿の壁」であって「CSRFの壁」ではない。** これはアクションのエンドポイントを推測されにくくし、未使用アクションを dead code elimination でクライアントバンドルから除去するための仕組みです。CSRFそのものを止める主役は、あくまで POST 限定と Origin/Host 照合です。IDの暗号化を「CSRF対策」として過大評価しないでください。

2. **クロージャの暗号化は別の話。** コンポーネント内に定義した Server Action が外側の変数を取り込む（クロージャ）場合、Next.js はその値を暗号化してクライアントに送ります。これは**機微な値の漏洩防止**であり、CSRFとは別レイヤーです。公式も「暗号化だけに頼って機微な値を守るべきではない」と注意しています。

3. **これは認可の代わりではない。** 公式は最も重要な但し書きを置いています——**「この改善は、認証層が欠けている場合のリスクを下げる。しかし依然として Server Actions は直接の POST リクエストで到達可能なものとして扱い、各アクションの中で認証と認可を検証すべきだ」。** つまり、Server Action は「UIに出していないから安全」ではなく、**externalから直接叩ける前提**で、内部で本人確認とリソース所有権の確認を必ず行う必要があります。

> **正直なまとめ。** Next.js の標準保護は「ちゃんとしている」。POST限定＋Origin/Host照合は、現実のCSRFの大半を素で弾きます。ですが公式自身が言うとおり、これは**認証・認可を肩代わりするものではなく**、**Server Actions という1経路にしか効きません。** 「足りない部分」を次節以降で具体化します。

---

## 3. 残る穴——標準保護が届かない4つの領域

標準保護が優秀でも、構造上カバーしない領域があります。ここを把握しないまま「Next.js が守ってくれる」と思い込むのが、最も危険な誤解です。

### 3-1. Route Handler（`route.ts`）は自動保護されない

最大の落とし穴がこれです。**前節の POST限定・Origin/Host照合は「Server Actions」の仕組みであって、`app/api/**/route.ts` の Route Handler には自動では適用されません。** Route Handler は素のHTTPエンドポイントで、`GET` も `POST` も `DELETE` も自分で定義でき、Next.js は出所のチェックを勝手には入れてくれません。公式が監査観点で「`proxy.ts` と `route.ts` は強力。従来手法で重点的に監査せよ」と名指ししているのは、まさにこの自由度ゆえです。

```ts
// app/api/account/route.ts — 脆弱：CookieだけでPOSTを認証し、出所を確認していない
import { cookies } from "next/headers";

export async function POST(req: Request) {
  const session = (await cookies()).get("session")?.value;
  if (!session) return new Response("unauthorized", { status: 401 });

  const { email } = await req.json();
  await updateAccountEmail(session, email); // ← evil.example からのCSRFでも実行されうる
  return Response.json({ ok: true });
}
```

このルートは、Server Actions と違って Origin/Host 照合を**持ちません**。罠サイトが被害者のブラウザから `fetch("https://yourapp.com/api/account", { method: "POST", credentials: "include" })` を撃てば、セッションCookieが自動添付され、メール変更が通ってしまう余地があります（CORS は単純リクエストの送信自体は止めません。止めるのはレスポンスの読み取りであって、副作用はサーバー側で先に起きます）。

### 3-2. Cookie ベース認証であること自体がCSRFの前提条件

CSRFが成立する根本は、**「ブラウザが自動で添付する資格情報（＝Cookie）だけで認証している」**ことです。逆に言えば、**Authorization ヘッダーの Bearer トークンや、リクエストボディの同期トークンなど「ブラウザが自動添付しない資格情報」で認証していれば、CSRFの主因は消えます。** 罠サイトは被害者のトークンをヘッダーに載せられないからです。Server Actions はフォーム送信＝Cookie前提なので、標準保護で出所を見るわけです。あなたの自前APIがCookie認証なら、同じ守りを自分で足す必要があります。

### 3-3. サブドメイン跨ぎ・`SameSite` の前提が崩れるケース

公式が頼りにする `SameSite` Cookie のデフォルトは強力ですが、万能ではありません。`SameSite=Lax`（多くのブラウザの既定）は**トップレベルナビゲーションのGET**では送られるため、状態変更をGETで行っていると素通りします。また、`Domain` 属性で `.example.com` のように**サブドメイン間でCookieを共有**していると、乗っ取られた、あるいは信頼の低いサブドメイン（`blog.example.com` など）が同一サイト扱いになり、Origin/Host が「同じ登録可能ドメイン」の範囲で攻撃面になりえます。`SameSite` の挙動は値ごとに異なるので、[MDN: SameSite cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie/SameSite) で正確に確認してください。

### 3-4. Origin ヘッダーが当てにならない経路・古いパターン

標準の Origin/Host 照合は、**ブラウザが Origin を正しく送る**前提に立ちます。現代ブラウザはPOSTでほぼ常に Origin を送りますが、(a) 非ブラウザクライアント（スクリプト・古いライブラリ）、(b) ごく古いブラウザ、(c) `<form action>` を使わずGETで状態変更する旧来パターン、では前提が崩れます。さらに、リバースプロキシやマルチレイヤ構成で**Host が production ドメインと食い違う**と、照合が誤って失敗（または設定次第で誤って通過）します。後者は公式が `allowedOrigins` の設定を案内している領域です（5-3で扱います）。

| 残る穴 | 標準保護が効かない理由 | 自分で足す防御（後述） |
|---|---|---|
| Route Handler の GET/POST | Server Actions の仕組みは route.ts に適用されない | 状態変更で Origin/Host 検証を自前実装（5-2） |
| Cookie だけの認証 | CSRFの前提そのもの | 非Cookie資格情報、または同期トークン（5-4） |
| サブドメイン共有 / GET変更 | SameSite=Lax はトップレベルGETを通す | 状態変更をPOSTに統一＋SameSite=Strict検討（5-1） |
| プロキシで Host 不一致 | Origin と Host がズレる | `serverActions.allowedOrigins` 設定（5-3） |

---

## 4. 攻撃が成立する様子——脆弱な実装を1本通す

抽象論を具体に落とします。次は「Server Actions を使っているのに、出所確認の効かない経路を作ってしまった」典型です。Server Action 自体は標準で守られますが、**そこから呼ばれる Route Handler に状態変更を逃がす**と保護の外に出ます。

```ts
// app/api/newsletter/unsubscribe/route.ts — 脆弱
// GETで状態変更している＝最悪のCSRF面。<img src> 一発で発火する
export async function GET(req: Request) {
  const url = new URL(req.url);
  const id = url.searchParams.get("id");      // ← クライアントが自由に指定
  await unsubscribe(id!);                       // ← 副作用をGETで実行
  return new Response("unsubscribed");
}
```

```html
<!-- evil.example に仕込むだけ。被害者がページを開いた瞬間に発火する -->
<img src="https://yourapp.com/api/newsletter/unsubscribe?id=42" />
```

この攻撃が成立する理由は3つ重なっています——**(1) 状態変更をGETで行っている**（POST限定の恩恵をそもそも受けていない）、**(2) Route Handler なので Origin/Host 照合が無い**、**(3) Cookie で本人確認しているため、被害者のブラウザが資格情報を自動添付する**。Server Actions の標準保護は、このどれも肩代わりしてくれません。経路を Route Handler に移した時点で、守りは全部あなたの責任になります。

---

## 5. 足すべき防御——最小コストで穴を塞ぐ

ここからが本題です。標準保護を土台に、残った穴だけを塞ぎます。やみくもに全部入れるのではなく、**自分のアプリがどの穴を持つか**を3節の表で見極めてから足すのが、コスト効率の良いやり方です。

### 5-1. 状態変更はPOST以上に統一し、Cookie に `SameSite` を付ける

まず、**副作用のある操作をGETで提供しない**。これだけで4-章のクラスの攻撃は消えます。そのうえで、認証Cookieに `SameSite` を明示します。Server Actions が頼る「ブラウザのデフォルト」に乗るのではなく、**自分のCookieで明示的に宣言する**のが堅牢です。

```ts
// 認証Cookieは SameSite を明示する。状態変更主体のアプリは Strict も検討
import { cookies } from "next/headers";

(await cookies()).set("session", token, {
  httpOnly: true,    // JSから読めない（XSS経由の盗難を緩和）
  secure: true,      // HTTPSのみ
  sameSite: "lax",   // クロスサイトの自動送信を抑制。外部遷移直後もログイン維持したいなら lax
  path: "/",
});
```

`Lax` と `Strict` の選択はUXとのトレードオフです。`Strict` は外部リンクから戻った直後にCookieが送られず「未ログイン」に見えるため、認証系では `Lax` が定番です。逆に、外部からの初回遷移でセッションが不要な管理画面などは `Strict` がより安全です。値ごとの正確な挙動は [MDN: SameSite cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie/SameSite) を一次情報にしてください。

### 5-2. 状態変更する Route Handler に Origin/Host 検証を自前で足す

Route Handler には Server Actions のような自動照合が無いので、**Server Actions と同じロジックを自分で書きます。** 「Origin が、自分の Host（プロキシ環境なら `X-Forwarded-Host`）と一致するか」を確認し、状態変更メソッドで弾きます。

```ts
// lib/csrf.ts — Server Actions の標準保護を Route Handler でも再現する
// 方針：状態変更メソッドのとき、Origin を自分の Host と照合する
const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);

export function assertSameOrigin(req: Request): void {
  if (SAFE_METHODS.has(req.method)) return; // 読み取りは対象外

  const origin = req.headers.get("origin");
  // プロキシ配下では X-Forwarded-Host が実ホスト。無ければ Host を使う
  const host = req.headers.get("x-forwarded-host") ?? req.headers.get("host");

  // Origin が無い状態変更は、出所を確認できないので拒否（fail-secure）
  if (!origin || !host) throw new Response("Forbidden", { status: 403 });

  // Origin のホスト部と、リクエストの宛先ホストが一致するか
  if (new URL(origin).host !== host) {
    throw new Response("Forbidden", { status: 403 });
  }
}
```

```ts
// app/api/account/route.ts — 修正：5-2 の検証を入口で適用
import { cookies } from "next/headers";
import { assertSameOrigin } from "@/lib/csrf";

export async function POST(req: Request) {
  assertSameOrigin(req); // ← この1行で別オリジン由来のPOSTを弾く

  const session = (await cookies()).get("session")?.value;
  if (!session) return new Response("unauthorized", { status: 401 });

  const { email } = await req.json();
  await updateAccountEmail(session, email);
  return Response.json({ ok: true });
}
```

ポイントは **「Origin が無い状態変更は拒否する（fail-secure）」** ことです。現代ブラウザはPOSTで Origin を送るので、Origin欠落は「ブラウザ外の経路」か「異常」を疑うのが安全側です。なお、複数のドメインから正規に叩かれるAPIなら、`host` 比較の代わりに**許可Originのallowlist**で照合します(`new Set(["https://app.example.com"]).has(origin)`)。この検証はミドルウェアに集約することもできますが、状態変更ルートが少数なら入口で明示する方が読み手に意図が伝わります。横断的に効かせる設計の話は[セキュリティヘッダーとCSPの実装ガイド](/blog/nextjs-security-headers-csp-nonce-middleware-guide)に整理しています。

### 5-3. リバースプロキシ環境では `serverActions.allowedOrigins` を設定する

Server Actions の標準照合は「Origin == Host」を見ます。**リバースプロキシや多層バックエンドで、ユーザーから見えるドメインとサーバーが認識する Host が食い違う**と、正規リクエストまで誤って中断されることがあります。公式はこのケース向けに設定オプションを用意しています。

```js
// next.config.js — プロキシ配下で正規Originを明示する（公式の案内どおり）
/** @type {import('next').NextConfig} */
module.exports = {
  experimental: {
    serverActions: {
      // 自サイトの正規オリジンだけを列挙する。ワイルドカードは最小限に
      allowedOrigins: ["app.example.com", "*.app.example.com"],
    },
  },
};
```

注意は**広げすぎないこと**です。`allowedOrigins` は「Origin/Host が一致しなくても通す」ための緩和であり、ここに余計なドメインを足すと、その分だけ標準保護を緩めることになります。必要な正規ドメインだけを列挙してください。

### 5-4. それでも不安な経路には double-submit / 同期トークン

Cookie 認証をやめられず、Origin 検証だけでは不安な高リスク操作（資金移動・権限変更など）には、古典的な**同期トークン（synchronizer token）**または **double-submit Cookie** を足します。考え方は「**ブラウザが自動添付しない値**を一致条件に加える」——攻撃者はその値を罠サイトから送れません。

```ts
// double-submit の最小例：ランダム値を Cookie とリクエストボディの両方で要求し、一致を見る
// 攻撃者はCookieは自動送信できても、その値を「読んでヘッダー/ボディに載せる」ことはできない
import { cookies } from "next/headers";

export async function POST(req: Request) {
  const cookieToken = (await cookies()).get("csrf")?.value;
  const sentToken = req.headers.get("x-csrf-token");

  if (!cookieToken || !sentToken || cookieToken !== sentToken) {
    return new Response("Forbidden", { status: 403 });
  }
  // 本処理…
}
```

ただし正直に言うと、**ほとんどのケースでは 5-1〜5-3（POST統一＋SameSite＋Origin/Host検証）で十分**です。トークン方式は実装と取り回しのコストが上がるので、Origin 検証が当てにできない経路や、規制要件で多重防御が要る場合の**追加層**と位置づけるのが現実的です。OWASP の検証観点（[OWASP ASVS](https://owasp.org/www-project-application-security-verification-standard/)）やテスト手順（[OWASP Web Security Testing Guide](https://owasp.org/www-project-web-security-testing-guide/)）も、CSRF対策は「単一の銀の弾丸」ではなく多層で検証することを求めています。

---

## 6. 認証・認可の再検証——CSRF対策では代替できない

公式の但し書きをもう一度強調します。**標準のCSRF保護も、5章で足した Origin 検証も、「このユーザーがこの操作・このリソースに対して権限を持つか」は一切判断しません。** Server Action は直接POSTで到達可能なので、UIに出していないアクションでも外部から叩けます。だから**各アクション/ハンドラの中で、認証と認可を毎回検証**します。

```ts
// app/actions.ts — Server Action 内で本人確認＋所有権を必ず再検証する
"use server";

import { auth } from "@/lib/auth";

export async function deletePost(postId: string) {
  // 1) 認証：ページの認証チェックはアクションに引き継がれない。ここで必ず確認
  const session = await auth();
  if (!session?.user) throw new Error("Unauthorized");

  // 2) 認可：postId は呼び出し側が自由に変えられる。所有権を確認（IDOR対策）
  const post = await getPost(postId);
  if (post.authorId !== session.user.id) throw new Error("Forbidden");

  await removePost(postId);
}
```

ここで `postId` を所有権で縛り忘れると、CSRF とは別系統の脆弱性（IDOR）になります。CSRF（出所の問題）と IDOR（権限の問題）は**直交する別々のリスク**であり、片方の対策がもう片方を肩代わりしません。この区別と、認可・RLS まで含めたアプリ層全体の地図は[Next.js × Supabase アプリケーションセキュリティ完全ガイド](/blog/nextjs-supabase-application-security-guide)に体系化しています。状態変更経路の濫用を抑えるレート制限は[サーバーレスのレート制限実装ガイド](/blog/nextjs-serverless-rate-limiting-vercel-guide)、リダイレクト先を悪用させない対策は[オープンリダイレクト対策ガイド](/blog/nextjs-open-redirect-callback-url-prevention-guide)に切り出しています。

---

## 7. これは「水平統制」——一律に固められる層

ここまでの対策の性質を、一段引いて捉え直します。CSRF / Origin 対策は、**アプリ固有のデータモデルを知らなくても、ライブラリと設定で一律に肩代わりできる「水平統制」**に属します。「このユーザーがこの行を所有するか」というアプリ固有の判断を要する認可（垂直リスク）とは、自動化の届く範囲が根本的に違います。

| 層 | 例 | 自動化の到達点 | 誰が守るか |
|---|---|---|---|
| **水平統制** | CSRF/Origin、SameSite Cookie、セキュリティヘッダー、レート制限 | 設定・ライブラリで**一律に**適用できる | プラットフォーム＋設定 |
| **垂直リスク** | 認可/IDOR、テナント分離、業務ロジック | 検出・警告まで。修正＝設計は人間 | 人間の設計 |

だからこそ、CSRF/Origin のような水平統制は「人間が毎回正しく書く」前提にせず、**一度固めて検出に番をさせる**のが正解です。私が公開している OSS **Aegis** は、この水平統制の検出を支援します——状態変更する Route Handler に Origin/Host 検証が無い箇所、GETで副作用を起こしている疑い、`SameSite` 未設定のCookie といった、**よくあるCSRF/Originの抜けを機械的に指摘**します。

```bash
# インストール不要・設定不要でスキャン（CSRF/Origin を含む水平統制の抜けを可視化）
npx @aegiskit/cli scan
```

> **過大主張はしません。** Aegis が見るのは「コードと設定の形」であって、あなたのアプリの「意味」ではありません。CSRF/Origin の**よくある抜けは検出できますが、CSRF対策が完璧であることは証明しません。** ましてや、CSRFと直交する認可（IDOR）の正しさは別途、設計と人間のレビューで守る必要があります。検出は人間の判断を**置き換えるものではなく、補完するもの**です。詳しくは[Aegis](/aegis)を参照してください。

---

## 8. 本番前チェックリスト

外注でもAI製でも、状態変更を扱うNext.jsアプリを本番に出す前に、最低限これだけは確認してください。

- [ ] **副作用のある操作をGETで提供していない**（GET変更はSameSite=Laxを素通りする最悪のCSRF面）
- [ ] **Route Handler（route.ts）の状態変更で Origin/Host を検証**している（Server Actionsの自動照合はroute.tsに効かない）
- [ ] 認証Cookieに **`SameSite` と `HttpOnly` と `Secure` を明示**している（ブラウザ既定に依存しきらない）
- [ ] リバースプロキシ配下なら **`serverActions.allowedOrigins` を正規ドメインだけ**で設定している
- [ ] **Origin が無い状態変更は拒否（fail-secure）** している（ブラウザ外経路を疑う）
- [ ] **各 Server Action / Route Handler 内で認証と認可を再検証**している（ページの認証はアクションに継がれない）
- [ ] 所有権チェック（IDOR対策）が状態変更にも入っている（CSRFとIDORは別物）
- [ ] サブドメインでCookieを共有しているなら、信頼境界と `SameSite=Strict` の要否を検討した
- [ ] 高リスク操作には必要に応じて **double-submit / 同期トークン** を追加している
- [ ] CSRF/Origin の抜けを **CIのスキャンで継続的に検出**している

発注者・レビュアーの視点で最も効くのは、**「状態変更APIは Origin を見ていますか？」「Server Action の外（route.ts）で副作用を起こしている経路はありますか？」「ページの認証はアクションの中でも再確認していますか？」**の3問です。良い開発者は即答できます。

---

## 9. どこまで標準で、どこから自分で——正直な線引き

最後に、正直に線を引きます。

**Next.js の Server Actions の標準保護は、ちゃんと機能します。** POST限定＋Origin/Host照合は、現代ブラウザの `SameSite` デフォルトと相まって、よくあるCSRFの大半を素で弾きます。「Server Actions のCSRF対策は脆弱だ」という主張は不正確で、**何も設定しない素の Server Actions でも、一定の耐性がある**のが事実です。

一方で、**その保護は Server Actions という1経路に限られ、認証・認可は肩代わりしません。** Route Handler の状態変更、Cookie だけの認証、サブドメイン共有、プロキシ配下の Host 不一致——これらは**あなたが明示的に守らない限り保護されません**。公式自身が「Server Actions は直POSTで到達可能として扱い、各アクション内で認証・認可を検証せよ」と言い切っています。**標準を過信せず、標準が届かない穴だけを 5章の最小コストで塞ぐ**——これが正しい姿勢です。

そして、ここでも線引きが要ります。**CSRF/Origin のような水平統制は自動化と検出で大半を機械的に潰せますが、認可（IDOR）やテナント分離の「正しさ」は、あなたのデータモデルを理解した人間にしか判断できません。** どこまで自分で固め、どこから専門家のレビューが要るか——既存の Next.js アプリのCSRF/Origin・認証フロー・認可の設計レビューが必要なら、[セキュリティ監査](/aegis/audit)で承ります。私自身、[環境分野のサーバーレス決済プラットフォーム](/case-studies/payment-platform-reliability)で、状態変更経路の出所検証・認証・所有権強制を、本番の決済信頼性レイヤーとして設計・運用してきました。**完全に安全にする魔法はありません。** あるのは、標準で守られる範囲を正確に知り、残りを多層で堅牢化する規律だけです。

---

## よくある質問（FAQ）

**Q. Server Actions を使っていれば、CSRF対策はもう要りませんか？**
A. 経路が Server Actions だけで、認証・認可を各アクション内で再検証しているなら、CSRFについては標準保護（POST限定＋Origin/Host照合）が大半を担います。ただし `route.ts` の状態変更を1本でも作った瞬間、その経路は自動保護の外なので、Origin/Host検証を自分で足す必要があります。

**Q. 暗号化アクションIDがあれば、エンドポイントを推測されないから安全では？**
A. それは秘匿の壁であって、CSRFの壁ではありません。CSRFを止める主役はPOST限定とOrigin/Host照合です。IDの暗号化は「未使用アクションをバンドルから消す」「パスを推測されにくくする」ための仕組みで、CSRF対策として過大評価しないでください。

**Q. CORS を設定すればCSRFは防げますか？**
A. 直接は防げません。CORSはクロスオリジンの**レスポンス読み取り**を制御しますが、CSRFが狙う**副作用はサーバー側で先に起きます**（単純リクエストの送信自体はCORSで止まりません）。CSRFには Origin/Host 検証や SameSite Cookie といった「出所・自動添付」を断つ対策が必要です。

**Q. `SameSite=Strict` にすれば Origin 検証は不要ですか？**
A. 大幅にリスクは下がりますが、不要とは言い切れません。`Strict` でも、信頼の低いサブドメインとCookieを共有している場合や、古いブラウザ・非ブラウザ経路では前提が崩れます。多層防御として、状態変更のOrigin/Host検証は併用するのが堅実です。

**Q. 個人開発や小規模でも、ここまでやるべきですか？**
A. 最小でも「状態変更をGETにしない」「route.tsの状態変更でOrigin検証」「認証Cookieに SameSite/HttpOnly/Secure」の3点は必ず。コストはわずかで、CSRFは古典的かつ自動化された攻撃が多いため、素の状態は危険です。

---

## まとめ：標準を正確に知り、残った穴だけを多層で塞ぐ

要点を整理します。

- **CSRFは「被害者のCookieで意図しない状態変更をさせる」攻撃。** 狙われるのは副作用のある操作で、対策の核は「GETで状態変更しない」「リクエストの出所を確かめる」。
- **Next.js の Server Actions は標準で POST限定＋Origin/Host照合＋暗号化アクションIDを持つ**（公式が示す範囲）。これは有用なCSRF耐性だが、**Server Actions という1経路にしか効かず、認証・認可は肩代わりしない。**
- **残る穴は4つ**——Route Handler(route.ts)のGET/POST、Cookieだけの認証、サブドメイン共有、プロキシ配下のHost不一致。標準保護はここに届かない。
- **足すべき防御**——状態変更のPOST統一、SameSite Cookieの明示、route.tsでのOrigin/Host検証、`serverActions.allowedOrigins`、必要なら double-submit。**ほとんどは前3つで足りる。**
- CSRF/Origin は**水平統制**で、設定とライブラリ・検出で一律に固められる。ただし**ツールは抜けの検出を助けるだけで、CSRF対策の完璧さも、直交する認可の正しさも証明しない。**

AIで速く作ること自体は正しい。**速く作ったものの「出所」と「権限」を、漏らさず固める**——その仕組みづくりや、既存のNext.jsアプリのCSRF/Origin・認証・認可レビューが必要であれば、お気軽にご相談ください。

---

## 参考資料

- [OWASP Top 10（Webアプリの主要リスク）](https://owasp.org/www-project-top-ten/)
- [OWASP Application Security Verification Standard（ASVS、検証観点）](https://owasp.org/www-project-application-security-verification-standard/)
- [OWASP Web Security Testing Guide（CSRFのテスト手順）](https://owasp.org/www-project-web-security-testing-guide/)
- [MDN — Set-Cookie の SameSite 属性（Lax/Strict/None の挙動）](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie/SameSite)
- [Next.js Docs（Server Actions のセキュリティと allowedOrigins）](https://nextjs.org/docs)
