メインコンテンツへスキップ
友田 陽大
アプリ層セキュリティ
Next.js
セキュリティ
TypeScript
アーキテクチャ設計

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

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

公開日
読了時間
23分
著者
友田 陽大
シェア

最初に結論を述べます。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)。

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

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)。憶測で「だから安全」と広げないことが重要です。

公式が「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エンドポイントで、GETPOSTDELETE も自分で定義でき、Next.js は出所のチェックを勝手には入れてくれません。公式が監査観点で「proxy.tsroute.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 は単純リクエストの送信自体は止めません。止めるのはレスポンスの読み取りであって、副作用はサーバー側で先に起きます)。

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 で正確に確認してください。

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

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

残る穴標準保護が効かない理由自分で足す防御(後述)
Route Handler の GET/POSTServer 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 に状態変更を逃がすと保護の外に出ます。

// 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");
}
<!-- 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で明示的に宣言するのが堅牢です。

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

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

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

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

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

// 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 });
  }
}
// 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の実装ガイドに整理しています。

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

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

// 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 を足します。考え方は「ブラウザが自動添付しない値を一致条件に加える」——攻撃者はその値を罠サイトから送れません。

// 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)やテスト手順(OWASP Web Security Testing Guide)も、CSRF対策は「単一の銀の弾丸」ではなく多層で検証することを求めています。


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

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

// 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 アプリケーションセキュリティ完全ガイドに体系化しています。状態変更経路の濫用を抑えるレート制限はサーバーレスのレート制限実装ガイド、リダイレクト先を悪用させない対策はオープンリダイレクト対策ガイドに切り出しています。


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

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

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

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

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

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


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

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

  • 副作用のある操作をGETで提供していない(GET変更はSameSite=Laxを素通りする最悪のCSRF面)
  • Route Handler(route.ts)の状態変更で Origin/Host を検証している(Server Actionsの自動照合はroute.tsに効かない)
  • 認証Cookieに SameSiteHttpOnlySecure を明示している(ブラウザ既定に依存しきらない)
  • リバースプロキシ配下なら 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・認証フロー・認可の設計レビューが必要なら、セキュリティ監査で承ります。私自身、環境分野のサーバーレス決済プラットフォームで、状態変更経路の出所検証・認証・所有権強制を、本番の決済信頼性レイヤーとして設計・運用してきました。完全に安全にする魔法はありません。 あるのは、標準で守られる範囲を正確に知り、残りを多層で堅牢化する規律だけです。


よくある質問(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・認証・認可レビューが必要であれば、お気軽にご相談ください。


参考資料

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・ 実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

この記事の対策、ツールで自動化できます

Next.js / Supabase のセキュリティ統制を、OSS の Aegis で自動化

この記事の対策の多くは、ミドルウェア1枚と静的解析で機械的に検出・強化できます。無料・MIT の Aegis なら、いまのプロジェクトを1コマンドからスキャンできます。設計が要る「縦のリスク」は監査でも承ります。

プロジェクト単位(請負)・技術顧問のどちらにも対応可能です。まずは30分の無料技術相談から。