3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Next.js + Supabase】訓練校システムの堅牢なセキュリティ実装ガイド:生徒の個人情報をどう守るか?

3
Last updated at Posted at 2025-12-26

【Next.js + Supabase】訓練校システムの堅牢なセキュリティ実装ガイド:生徒の個人情報をどう守るか?

こんにちは。今回は、私たちが開発している「訓練校 進捗管理システム」の裏側で動いているセキュリティ実装について、技術的な詳細を公開します。

このシステムでは、受講生の履歴書や職務経歴書、成績といった極めて重要な個人情報を扱います。「動けばいい」では済まされない、堅牢な守りが必要です。

今回は、アプリケーション層(Next.js)データベース層(Supabase)、そして**インフラ層(Docker)**の3つの視点から、私たちが実践したセキュリティ対策を徹底解説します。


🛡️ 全体像:多層防御のアプローチ

セキュリティは「何か一つやればOK」というものではありません。攻撃者は常に弱いところを探して侵入しようとします。そのため、私たちは以下の図のような多層防御のアプローチを採用しています。


1. アプリケーション層の防御(Next.js)

まずは「玄関」であるアプリケーション側の対策です。

1.1 HTTPセキュリティヘッダーでブラウザを守る

next.config.ts にて、HTTPレスポンスヘッダーを厳格に設定しています。これは、ブラウザに対して「このサイトではこれをしてはいけない」と指示を出すようなものです。

特に重要なのが Content-Security-Policy (CSP) です。

// next.config.ts(一部抜粋)
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self' ...; frame-ancestors 'none';"
  }
];

  • XSS(クロスサイトスクリプティング)対策: 許可されたドメイン(自分自身やGoogle Fontsなど)以外からのスクリプト実行をブロックします。
  • クリックジャッキング対策: frame-ancestors 'none' により、他のサイトがこのサイトを iframe で埋め込むことを禁止します。透明なボタンを重ねてクリックさせる攻撃を防ぎます。

1.2 「入力はすべて疑え」:Zodによるバリデーション

ユーザーからの入力データは、サーバーに到達する前に必ず検証します。ここでは Zod というライブラリを使用し、型安全なスキーマ定義を行っています。

// schemas.ts
export const UpdateStudentProfileSchema = z.object({
  fullName: z.string().min(1).max(100), // 文字数制限
  courseId: z.string().uuid(),          // UUID形式チェック
});

これにより、不正な形式のデータや、想定外に長い文字列(バッファオーバーフロー攻撃のきっかけになるもの)を入り口で遮断します。

1.3 CSRF(クロスサイトリクエストフォージェリ)対策

ログイン済みのユーザーのブラウザを悪用して、勝手に処理を実行させる攻撃を防ぐため、Server Actions 実行時に「リクエストの送信元」を検証しています。

// security.ts
export async function validateRequestOrigin(): Promise<boolean> {
    const origin = headersList.get("origin");
    const host = headersList.get("host");
    // OriginとHostが一致しない場合は拒否
    return originUrl.host === host;
}


2. データベース層の防御(Supabase)

次に、データの保管庫であるSupabase側の対策です。ここが「最後の砦」となります。

2.1 Row Level Security (RLS) の徹底

Supabase(PostgreSQL)の最強の機能が RLS(行レベルセキュリティ) です。
これは、「同じテーブルへのアクセスでも、誰がアクセスするかによって見える行(データ)を変える」機能です。

私たちは、すべてのテーブルでRLSを有効化しています。

具体例:application_attachments(応募書類)テーブル

アクセスする人 権限 (Policy) 結果
管理者 (Admin) is_admin() 全員の書類が見える・削除できる
学生 (Student) student_id = auth.uid() 自分の書類だけが見える・アップロードできる
部外者 (ポリシーなし) 何も見えない(エラーになる)

SQLレベルでこの制御が行われているため、万が一アプリケーション側にバグがあっても、他人のデータが流出することはありません。

2.2 ファイルアップロードの厳格な管理

履歴書などのファイルアップロード(Storage)にもRLSを適用しています。さらに、ファイル名による攻撃を防ぐため、サーバー側でファイル名を再生成しています。

  1. 拡張子チェック: 許可されたファイル形式か?
  2. サイズ制限: 5MB以下か?
  3. 所有権確認: アップロードしようとしている場所は自分のフォルダか?
  4. ファイル名無害化: user-input.pdf[timestamp]-[random].pdf に変換

3. インフラ・監査層の防御

3.1 Dockerコンテナの堅牢化(Rootレス)

今回、Dockerfile の記述を見直し、セキュリティを強化しました。
デフォルトではDockerコンテナは root 権限で動きますが、これはセキュリティリスクとなります。

改善点:非rootユーザーでの実行

# Dockerfile(改善版)
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# ...ファイルの所有権変更...

USER nextjs # ← ここ重要!

これにより、万が一コンテナ内に侵入されても、攻撃者は権限が制限されているため、システム全体への被害を最小限に抑えることができます。

3.2 ログ・監査証跡(Audit Log)

「誰が」「いつ」「何をしたか」を記録することは、事後追跡のために不可欠です。
ただし、パスワードなどの機密情報をログに残してはいけません

私たちはカスタムロガーを実装し、自動的に機密情報をマスク(隠蔽)しています。

// ログ出力イメージ
{
  "action": "login_attempt",
  "email": "student@example.com",
  "password": "********", // 自動的にマスクされる
  "ip_address": "192.168.1.1"
}


4. 今後の課題:パスワード漏洩検知

現在、Supabaseの "Leaked Password Protection"(漏洩パスワード保護)が無効になっています。これは、「過去に他のサイト等で流出したことが知られているパスワード」の使用をブロックする機能です。
次回のアップデートで、この機能を有効化し、辞書攻撃やパスワードリスト攻撃への耐性をさらに高める予定です。


📝 セキュリティ実装チェックリスト

最後に、今回の開発で使用したセキュリティチェックシートを公開します。皆さんの開発案件でもぜひ活用してください。

✅ アプリケーション(Next.js)

  • HTTPヘッダー: CSP, HSTS, X-Frame-Options, X-Content-Type-Options が設定されているか
  • バリデーション: すべての入力値に対して Zod 等で型・長さ・形式の検証を行っているか
  • CSRF対策: Server Actions で Origin/Host ヘッダーの検証を行っているか
  • ファイルアップロード: ファイルサイズ、拡張子、MIMEタイプをサーバー側で検証しているか

✅ データベース(Supabase)

  • RLS有効化: すべてのテーブルでRLSが有効になっているか(alter table ... enable row level security
  • ポリシー定義: 「誰が」「何を」して良いか、最小権限の原則に基づき定義されているか
  • Storage RLS: ファイルストレージ(バケット)にも適切なポリシーが適用されているか
  • 関数セキュリティ: SECURITY DEFINER を使用する際、search_path を設定しているか

✅ インフラ(Docker / AWS)

  • 非root実行: コンテナは非rootユーザー(例: nextjs)で実行されているか
  • 機密情報の除外: .dockerignore.env.git が含まれているか
  • 環境変数: APIキーなどの機密情報はコードに埋め込まず、環境変数で注入しているか
  • ヘルスチェック: コンテナの死活監視が設定されているか

✅ 運用・監視

  • ログのマスキング: パスワードやトークンがログに出力されていないか
  • 監査ログ: 重要な操作(削除、権限変更など)が記録されているか

まとめ

セキュリティは「機能」ではなく「品質」です。
受講生の皆さんが安心して学習に取り組み、素晴らしいキャリアを築くための第一歩として、私たちはこのシステムを安全に守り続けます。

もし、このセキュリティ実装について詳しく知りたい点があれば、お気軽に質問してください!


3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?