Next.jsセキュリティ初心者ガイド|学び始めたその日に知っておきたい基礎知識

Next.jsの学習を始めたばかりの方の多くは、まず「動くものを作る」ことに集中します。ルーティングができた、データが表示できた、フォームが送信できた——そこまで来ると、次に気になるのが「このまま公開して大丈夫なのか」という不安です。実は、Next.jsのApp RouterとServer Actionsは非常に便利な反面、フロントエンドとバックエンドの境界があいまいになりやすく、初心者がセキュリティ上のミスに気づかないまま本番公開してしまうケースが少なくありません。この記事では、Next.jsを学び始めたばかりの開発者が最初に押さえておくべきセキュリティの基礎を、実際に起こりやすい落とし穴と具体的なコード例とともに解説します。

なぜNext.jsではセキュリティの考え方が変わるのか

従来のReactアプリ(Create React Appなど)では、ブラウザで動くコードとサーバーで動くコードは物理的に別のプロジェクト・別のリポジトリに分かれていることがほとんどでした。ところがNext.jsのApp Routerでは、Server ComponentsとClient Componentsが同じファイルツリーの中に混在し、さらにServer Actionsという「サーバー上で実行される関数をクライアントから直接呼び出せる」仕組みまで用意されています。

これは開発体験としては革命的ですが、裏を返せば「サーバー専用のつもりで書いたコードが、実はクライアントの手が届く場所にある」という事故が起きやすくなったということでもあります。境界線が見えにくくなった分、どこからどこまでが「信頼できないユーザーの入力」で、どこからが「サーバーが保証する安全な処理」なのかを、自分の頭の中で明確に線引きする習慣が必要になります。

Server ComponentsとClient Componentsの境界

Server Componentはサーバー上でのみレンダリングされ、そのコード自体はブラウザに送られません。一方Client Componentは("use client"ディレクティブを付けたファイル)ブラウザ側でも実行されるため、そこに書いた変数やロジックは原理的にすべてユーザーに見える状態になります。この違いを意識せずに「動いたからOK」で進めてしまうと、次章で説明するような情報漏洩につながります。

Server Actionsは「隠れたAPIエンドポイント」である

Server Actionをexportすると、Next.jsは裏側でそれ専用のエンドポイントを自動生成します。つまりServer Actionは見た目こそ普通の関数ですが、実態は誰でも直接HTTPリクエストを送って呼び出せる公開APIです。「ただのサーバー側関数だから安全」という思い込みは、初心者が最も陥りやすい誤解の一つです。

初心者が陥りやすい5つの落とし穴

1. Client Componentに機密データをそのまま渡してしまう

データベースから取得したユーザーオブジェクトを、パスワードハッシュや内部IDごとClient Componentのpropsに渡してしまうケースです。画面には表示していなくても、ブラウザの開発者ツールでReactのコンポーネントツリーやネットワークタブを見れば、渡された全データが確認できてしまいます。

2. Server Actionを「関数」として扱い、認証チェックを省略する

前述の通りServer Actionsは公開エンドポイントです。同じページ内から呼んでいるから安全、ログイン済みユーザーしかボタンを押せないから安全、という考えは誤りです。Server Action自体が独立したリクエストとして直接叩かれる前提で、関数の冒頭に必ずセッション検証を書く必要があります。

3. 環境変数の公開範囲を理解せずAPIキーを漏らす

NEXT_PUBLIC_接頭辞を付けた環境変数は、ビルド時にクライアントのJavaScriptバンドルへ埋め込まれ、誰でも読める状態になります。決済APIの秘密鍵やDBの接続文字列に誤ってこの接頭辞を付けてしまうと、それだけで重大な情報漏洩になります。

4. クライアント側バリデーションだけで安心してしまう

フォームにzodやreact-hook-formでバリデーションを実装すると「入力チェックは万全」と感じがちですが、クライアント側の検証はあくまでユーザー体験の向上のためのものです。悪意あるユーザーはブラウザの検証を迂回して直接Server Actionやルートハンドラーにリクエストを送れるため、サーバー側でも同じ検証を必ず行う必要があります。

5. .envファイルや秘密情報をリポジトリにコミットしてしまう

学習用のサンプルコードをそのままGitHubに公開する際、.env.local.env.gitignoreに入れ忘れて秘密鍵ごと公開してしまう事故も初心者に非常に多いパターンです。

今日から実践できる基本の対策

server-onlyパッケージでサーバー専用コードを隔離する

秘密情報やDBアクセスを含むモジュールの先頭にimport "server-only"を書いておくと、そのファイルが誤ってClient Componentからimportされた場合にビルドエラーとして検知できます。仕組みとして事故を未然に防げるため、学習の初期段階から習慣化しておくと安心です。

// lib/db.ts
import "server-only";

export const getSecretApiKey = () => {
  return process.env.SECRET_API_KEY;
};

DTO(Data Transfer Object)で必要な情報だけを渡す

DBから取得したオブジェクトをそのままClient Componentへ渡すのではなく、画面表示に必要なプロパティだけを抽出した専用の型を作って返す設計にします。これにより「うっかり渡しすぎる」事故を構造的に防げます。

export interface UserProfileDTO {
  id: string;
  name: string;
  avatarUrl: string;
}

export async function getProfileDTO(userId: string): Promise<UserProfileDTO> {
  const user = await getUserById(userId); // パスワードハッシュ等を含む生データ
  return {
    id: user.id,
    name: user.name,
    avatarUrl: user.avatarUrl,
  };
}

Server Actionsの先頭で必ずセッションと権限を検証する

Server Actionは独立した公開エンドポイントとして振る舞う前提で実装します。認証されていない、または権限を持たないリクエストは処理の最初の段階で拒否します。

"use server";

export async function deletePostAction(postId: string) {
  const session = await verifySession();

  if (!session?.user || session.user.role !== "admin") {
    throw new Error("権限がありません");
  }

  await db.post.delete({ where: { id: postId } });
}

サーバー側でも入力検証を行う

zodなどのスキーマ検証ライブラリを使い、クライアントで使ったスキーマと同じ(もしくはそれ以上に厳格な)検証をServer Action内でも実行します。フォームの型とAPIの型を共有できるzodは、Next.jsとの相性が良く初心者にも導入しやすい選択肢です。

import { z } from "zod";

const commentSchema = z.object({
  postId: z.string().uuid(),
  body: z.string().min(1).max(500),
});

export async function addCommentAction(input: unknown) {
  const parsed = commentSchema.safeParse(input);
  if (!parsed.success) {
    throw new Error("入力内容が不正です");
  }
  // ここから先は検証済みデータのみを扱う
}

CSRF対策の基本を理解しておく

Next.jsのServer Actionsは、Same-Siteクッキーの挙動に加えて、v14以降はリクエストのOriginヘッダーとHostヘッダーを自動で照合する仕組みを備えています。これにより従来型のCSRF攻撃の多くはフレームワーク側である程度緩和されますが、リバースプロキシ構成でホスト情報が正しく引き継がれていない場合など、設定次第でこの保護が効かなくなることもあるため「自動で守られているはず」と過信せず、本番環境でヘッダーが期待通り渡っているかを確認する意識を持つことが大切です。

学習が進んだら押さえておきたい発展的なトピック

基礎が身についてきたら、次のステップとして以下も学んでおくと理解がさらに深まります。

  • Taint API:特定のオブジェクトや値に「クライアントに渡してはいけない」というマークを付け、誤って渡された場合にランタイムエラーとして検知する実験的な機能です。
  • データアクセス層の設計:HTTP API経由でバックエンドと通信する構成、Next.js内にデータアクセス層(DAL)を設ける構成、コンポーネント単位で都度DBアクセスする構成など、アプリの規模やアクセス数に応じてどの設計を選ぶかは、セキュリティと保守性の両面に影響する重要な判断です。
  • Content Security Policy(CSP):XSS対策の多層防御として、どの種類のスクリプトやリソースの読み込みを許可するかをHTTPヘッダーで宣言する仕組みです。

公開前セルフチェックリスト

  • Client Componentに渡しているpropsに、パスワードハッシュ・内部ID・他人の個人情報など不要な機密データが含まれていないか
  • すべてのServer Actionの先頭でセッション検証・権限チェックを行っているか
  • NEXT_PUBLIC_を付けている環境変数が、本当にクライアントに公開してよい値だけか
  • フォームの検証をクライアント側だけでなくサーバー側(Server ActionやRoute Handler)でも行っているか
  • .env系ファイルが.gitignoreに含まれており、リポジトリ履歴にも秘密情報が残っていないか
  • 秘密情報を扱うモジュールにimport "server-only"を設定しているか
  • 本番環境でOrigin/Hostヘッダーの検証が意図通り機能しているか(プロキシ構成を含めて)確認したか

まとめ

Next.jsはServer ComponentsとServer Actionsによって、フロントエンドとバックエンドの境界を意識せずに開発を進められる強力なフレームワークです。しかしその利便性は、境界を意識しなくなることによる事故と表裏一体でもあります。初心者のうちは特に、「Client Componentに渡した値はすべて公開される」「Server Actionsは独立した公開APIである」という2つの原則さえ体に染み込ませておけば、この記事で紹介した落とし穴の大半は回避できます。まずは今回のチェックリストを手元の学習プロジェクトに当てはめてみることから始めてみてください。

コメント

タイトルとURLをコピーしました