Web アプリ側: 生徒パスワード設定 API の実装方法
ネイティブアプリから呼び出される /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": "エラーメッセージ" }
npm install firebase-admin
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 };
重要: サービスアカウントキーファイル名は、プロジェクトに合わせて変更してください。
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", }, } ); }
サービスアカウントキーファイルを Git から除外します。.gitignoreに以下を追加:
# Firebase Admin SDK service account keys
*-firebase-adminsdk-*.json
service-account-key.json
ルートディレクトリにサービスアカウントキーファイル(例:service-account-key.json)を配置します。
または、.env.localに以下を追加:
FIREBASE_SERVICE_ACCOUNT_KEY={"type":"service_account","project_id":"..."}
注意: JSON を 1 行の文字列として設定する必要があります。
FIREBASE_SERVICE_ACCOUNT_KEYProduction, Preview, Development すべて(または必要な環境のみ)重要:
src/app/api/students/set-password/route.jsからsrc/lib/firebase-admin.jsへの相対パスは:
import { admin } from "../../../../lib/firebase-admin";
パスが正しくない場合は、ディレクトリ構造を確認してください。
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 || "パスワードの設定に失敗しました", }); } }
studentsとstudents_checkコレクションへの書き込み権限実装した API Route は以下のエラーケースを処理します:
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": "エラーメッセージ" }
firebase-adminパッケージがインストールされているかsrc/lib/firebase-admin.jsが正しく作成されているかfirebase-admin.jsのファイル名と一致しているか)src/app/api/students/set-password/route.js).gitignoreにサービスアカウントキーファイルが追加されているかFIREBASE_SERVICE_ACCOUNT_KEYが設定されているかnpm run build).gitignoreで確実に除外されているか確認してください。