パスワード設定 API Route 実装ガイド

Web アプリ側: 生徒パスワード設定 API の実装方法

Web アプリ側: パスワード設定 API Route 実装ガイド

概要

ネイティブアプリから呼び出される /api/students/set-password エンドポイントを Web アプリ側に実装します。

この API Route は、Firebase Admin SDK を使用して生徒のパスワードを設定し、Firestore のstudentsコレクションのrequiresPasswordSetupフィールドをfalseに更新します。

エンドポイント仕様

  • パス: POST /api/students/set-password
  • リクエストボディ:
{ "email": "student@example-hs.ac.jp", "password": "newPassword123" }
  • レスポンス(成功):
{ "success": true, "message": "パスワードを設定しました" }
  • レスポンス(エラー):
{ "error": "エラーメッセージ" }

実装手順(Next.js App Router)

1. 必要なパッケージのインストール

npm install firebase-admin

2. Firebase Admin SDK 初期化モジュールの作成

src/lib/firebase-admin.js を作成します:

import admin from "firebase-admin"; import fs from "fs"; import path from "path"; // Firebase Admin SDKの初期化(まだ初期化していない場合) if (!admin.apps.length) { let serviceAccount; // 本番環境(Vercel)では環境変数から読み込む if (process.env.FIREBASE_SERVICE_ACCOUNT_KEY) { serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_KEY); } else { // 開発環境ではローカルファイルから読み込む try { const serviceAccountKeyFileName = process.env.SERVICE_ACCOUNT_KEY_FILE || "service-account-key.json"; const filePath = path.join(process.cwd(), serviceAccountKeyFileName); const fileContents = fs.readFileSync(filePath, "utf8"); serviceAccount = JSON.parse(fileContents); } catch (error) { console.error( "Firebase Admin SDK初期化エラー: サービスアカウントキーが見つかりません" ); console.error( "環境変数 FIREBASE_SERVICE_ACCOUNT_KEY を設定するか、ルートディレクトリにサービスアカウントキーファイルを配置してください。" ); throw error; } } admin.initializeApp({ credential: admin.credential.cert(serviceAccount), }); } export { admin };

重要: サービスアカウントキーファイル名は、プロジェクトに合わせて変更してください。

3. API Route の実装

src/app/api/students/set-password/route.js を作成します:

import { NextResponse } from "next/server"; import { admin } from "../../../../lib/firebase-admin"; const auth = admin.auth(); const db = admin.firestore(); export async function POST(request) { try { const { email, password } = await request.json(); // バリデーション if (!email || !email.trim()) { return NextResponse.json( { error: "メールアドレスが必要です" }, { status: 400 } ); } if (!password || password.length < 6) { return NextResponse.json( { error: "パスワードは6文字以上である必要があります" }, { status: 400 } ); } const normalizedEmail = email.trim().toLowerCase(); // 1. students_checkコレクションから生徒データを確認 const studentsCheckRef = db.collection("students_check"); const studentsCheckSnapshot = await studentsCheckRef .where("email", "==", normalizedEmail) .get(); if (studentsCheckSnapshot.empty) { return NextResponse.json( { error: "このメールアドレスの生徒アカウントが見つかりませんでした" }, { status: 404 } ); } const studentCheckDoc = studentsCheckSnapshot.docs[0]; const studentCheckData = studentCheckDoc.data(); // requiresPasswordSetupがfalseの場合は、既にパスワード設定済み if (!studentCheckData.requiresPasswordSetup) { return NextResponse.json( { error: "このアカウントは既にパスワードが設定されています" }, { status: 400 } ); } // 2. Firebase Authでユーザーが存在するか確認 let user; try { user = await auth.getUserByEmail(normalizedEmail); } catch (error) { if (error.code === "auth/user-not-found") { // ユーザーが存在しない場合は作成 user = await auth.createUser({ email: normalizedEmail, password: password, emailVerified: false, }); } else { throw error; } } // 3. パスワードを更新(既存ユーザーの場合) if (user) { await auth.updateUser(user.uid, { password: password, }); } // 4. Firestoreのstudentsコレクションを更新 const studentsRef = db.collection("students"); const studentsSnapshot = await studentsRef .where("email", "==", normalizedEmail) .get(); if (!studentsSnapshot.empty) { const studentDoc = studentsSnapshot.docs[0]; await studentDoc.ref.update({ requiresPasswordSetup: false, updatedAt: admin.firestore.FieldValue.serverTimestamp(), }); } // 5. students_checkコレクションも更新 await studentCheckDoc.ref.update({ requiresPasswordSetup: false, updatedAt: admin.firestore.FieldValue.serverTimestamp(), }); return NextResponse.json({ success: true, message: "パスワードを設定しました", }); } catch (error) { console.error("パスワード設定エラー:", error); return NextResponse.json( { error: error.message || "パスワードの設定に失敗しました" }, { status: 500 } ); } } // GETメソッド(テスト用・デバッグ用) export async function GET(request) { return NextResponse.json( { message: "このエンドポイントはPOSTメソッドのみをサポートしています", endpoint: "/api/students/set-password", method: "POST", description: "生徒のパスワードを設定するためのAPIエンドポイントです", }, { status: 405 } ); } // CORS設定(必要に応じて) export async function OPTIONS(request) { return NextResponse.json( {}, { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type", }, } ); }

4. .gitignore の設定

サービスアカウントキーファイルを Git から除外します。.gitignoreに以下を追加:

# Firebase Admin SDK service account keys
*-firebase-adminsdk-*.json
service-account-key.json

5. 環境変数の設定

開発環境

ルートディレクトリにサービスアカウントキーファイル(例:service-account-key.json)を配置します。

または、.env.localに以下を追加:

FIREBASE_SERVICE_ACCOUNT_KEY={"type":"service_account","project_id":"..."}

注意: JSON を 1 行の文字列として設定する必要があります。

本番環境(Vercel)

  1. Vercel ダッシュボードにアクセス
  2. プロジェクトの「Settings」→「Environment Variables」に移動
  3. 新しい環境変数を追加:
    • Name: FIREBASE_SERVICE_ACCOUNT_KEY
    • Value: サービスアカウントキー JSON ファイルの内容全体を 1 行の JSON 文字列として貼り付け
    • Environment: Production, Preview, Development すべて(または必要な環境のみ)

重要:

  • JSON 全体を1 行の文字列として設定してください
  • 環境変数の値をコピーする際は、サービスアカウントキーファイル全体の内容をコピーしてください

6. パスの確認

src/app/api/students/set-password/route.jsからsrc/lib/firebase-admin.jsへの相対パスは:

import { admin } from "../../../../lib/firebase-admin";

パスが正しくない場合は、ディレクトリ構造を確認してください。

Next.js Pages Router 形式での実装

Next.js Pages Router を使用している場合は、pages/api/students/set-password.jsを作成します。

ファイル構造

pages/api/students/set-password.js

実装コード

import { admin } from "../../lib/firebase-admin"; // Firebase Admin初期化済みモジュール export default async function handler(req, res) { if (req.method !== "POST") { return res.status(405).json({ error: "Method Not Allowed" }); } try { const { email, password } = req.body; // バリデーション if (!email || !email.trim()) { return res.status(400).json({ error: "メールアドレスが必要です" }); } if (!password || password.length < 6) { return res.status(400).json({ error: "パスワードは6文字以上である必要があります", }); } const normalizedEmail = email.trim().toLowerCase(); // 1. students_checkコレクションから生徒データを確認 const db = admin.firestore(); const studentsCheckRef = db.collection("students_check"); const studentsCheckSnapshot = await studentsCheckRef .where("email", "==", normalizedEmail) .get(); if (studentsCheckSnapshot.empty) { return res.status(404).json({ error: "このメールアドレスの生徒アカウントが見つかりませんでした", }); } const studentCheckDoc = studentsCheckSnapshot.docs[0]; const studentCheckData = studentCheckDoc.data(); // requiresPasswordSetupがfalseの場合は、既にパスワード設定済み if (!studentCheckData.requiresPasswordSetup) { return res.status(400).json({ error: "このアカウントは既にパスワードが設定されています", }); } // 2. Firebase Authでユーザーが存在するか確認 const auth = admin.auth(); let user; try { user = await auth.getUserByEmail(normalizedEmail); } catch (error) { if (error.code === "auth/user-not-found") { // ユーザーが存在しない場合は作成 user = await auth.createUser({ email: normalizedEmail, password: password, emailVerified: false, }); } else { throw error; } } // 3. パスワードを更新(既存ユーザーの場合) if (user) { await auth.updateUser(user.uid, { password: password, }); } // 4. Firestoreのstudentsコレクションを更新 const studentsRef = db.collection("students"); const studentsSnapshot = await studentsRef .where("email", "==", normalizedEmail) .get(); if (!studentsSnapshot.empty) { const studentDoc = studentsSnapshot.docs[0]; await studentDoc.ref.update({ requiresPasswordSetup: false, updatedAt: admin.firestore.FieldValue.serverTimestamp(), }); } // 5. students_checkコレクションも更新 await studentCheckDoc.ref.update({ requiresPasswordSetup: false, updatedAt: admin.firestore.FieldValue.serverTimestamp(), }); return res.status(200).json({ success: true, message: "パスワードを設定しました", }); } catch (error) { console.error("パスワード設定エラー:", error); return res.status(500).json({ error: error.message || "パスワードの設定に失敗しました", }); } }

必要な権限と設定

  1. Firebase Admin SDK: サーバー側で Firebase Admin SDK を初期化
  2. サービスアカウントキー: Firebase Admin SDK の認証情報
  3. Firestore 権限: studentsstudents_checkコレクションへの書き込み権限
  4. Firebase Auth 権限: ユーザーの作成・パスワード更新権限

エラーハンドリング

実装した API Route は以下のエラーケースを処理します:

  • メールアドレス未入力(400)
  • パスワードが 6 文字未満(400)
  • 生徒アカウントが見つからない(404)
  • 既にパスワードが設定済み(400)
  • サーバー内部エラー(500)

テスト方法

cURL でテスト

curl -X POST https://your-domain.com/api/students/set-password \ -H "Content-Type: application/json" \ -d '{ "email": "student@example-hs.ac.jp", "password": "newPassword123" }'

期待されるレスポンス

成功時:

{ "success": true, "message": "パスワードを設定しました" }

エラー時:

{ "error": "エラーメッセージ" }

実装時の確認事項

  1. firebase-adminパッケージがインストールされているか
  2. src/lib/firebase-admin.jsが正しく作成されているか
  3. ✅ サービスアカウントキーファイル名が正しいか(firebase-admin.jsのファイル名と一致しているか)
  4. ✅ API Route のパスが正しいか(src/app/api/students/set-password/route.js
  5. ✅ import パスが正しいか(相対パスの階層を確認)
  6. .gitignoreにサービスアカウントキーファイルが追加されているか
  7. ✅ Vercel の環境変数FIREBASE_SERVICE_ACCOUNT_KEYが設定されているか
  8. ✅ ビルドが成功するか(npm run build

注意事項

  1. セキュリティ: この API Route は認証なしでアクセス可能です。必要に応じて、レート制限や認証を追加してください。
  2. パスワード要件: 6 文字以上のみチェックしていますが、必要に応じてより厳しい要件を追加できます。
  3. ログ: 本番環境では、機密情報(パスワードなど)をログに出力しないでください。
  4. サービスアカウントキー: 絶対に Git にコミットしないでください。.gitignoreで確実に除外されているか確認してください。
  5. 環境変数: Vercel に環境変数を設定する際は、JSON 全体を 1 行の文字列として設定してください。