はじめに
Web アプリを作るとき、ユーザーが「ログイン」したことを安全に認識し続ける仕組みは必須です。
Firebase Authentication は、メール・パスワードや Google・GitHub などの外部プロバイダーを簡単に統合できるサービスですが、認証情報だけでは「セッション」の維持はできません。
本記事では、Firebase Authentication と セッション管理 の全体像を初心者向けに解説し、実装例を TypeScript(Next.js)で示します。
1. 認証とセッションの違い
| 用語 | 役割 | 代表的なデータ |
|---|---|---|
| 認証 (Authentication) | ユーザーが誰かを確認する | ID トークン、リフレッシュトークン |
| セッション (Session) | 認証状態を継続的に保持する | セッションクッキー、サーバー側セッションストア |
Firebase が提供するのは ID トークン と リフレッシュトークン です。
ID トークンは JWT 形式で、クライアント側に保存して API へ付与できますが、有効期限が 1 時間程度と短いです。
そこで、セッション管理 でトークンを安全に保持し、期限切れ時に自動でリフレッシュする仕組みが必要になります。
2. 推奨アーキテクチャ
┌─────────────┐ ┌─────────────────────┐
│ クライアント│ │ Next.js API │
│ (React/Next)│───►───►│ (Node.js Runtime) │
└─────▲───────┘ └───────▲───────────────┘
│ │
│ Firebase Auth SDK │
▼ │
┌─────────────┐ HTTP Only Cookie │
│ Firebase │◄───────────────────────┘
│ Auth Server│
└─────────────┘
-
クライアントは Firebase SDK でサインインし、
getIdToken()で取得した ID トークンを Next.js の API へ POST。 - APIはトークンを検証し、サーバー側で HTTP Only Cookie に セッション ID を設定。
- 以降のリクエストは Cookie だけで認証が通り、サーバーは Cookie のセッション ID からユーザー情報を取得。
- トークン有効期限が切れたら、サーバー側でリフレッシュトークンを使って新しい ID トークンを取得し、Cookie を更新。
この構成のメリットは:
- XSS 対策: HTTP Only Cookie なので JavaScript から直接アクセスできない。
-
SSR 対応: Next.js の
getServerSidePropsでも Cookie が読めるため、サーバーサイドレンダリング時にユーザー情報を取得可能。 - トークンの自動リフレッシュ: サーバーがリフレッシュトークンを管理するので、クライアント側で期限切れを意識しなくて済む。
3. 実装ステップ
3.1 Firebase プロジェクトの作成と設定
- Firebase コンソールでプロジェクトを作成。
- Authentication → 「サインイン方法」から「メール/パスワード」や「Google」などを有効化。
- 「プロジェクト設定」→「一般」から Web アプリの Firebase 設定オブジェクトを取得。
3.2 Next.js に Firebase SDK を導入
npm install firebase @firebase/app @firebase/auth
lib/firebase.ts に初期化コードを書きます。
// lib/firebase.ts
import { initializeApp, getApps } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
projectId: "YOUR_PROJECT_ID",
// 他の項目は省略
};
export const firebaseApp = !getApps().length ? initializeApp(firebaseConfig) : getApps()[0];
export const auth = getAuth(firebaseApp);
3.3 サインイン UI(クライアント側)
// pages/login.tsx
import { signInWithEmailAndPassword, getIdToken } from "firebase/auth";
import { auth } from "../lib/firebase";
import { useState } from "react";
export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
const userCred = await signInWithEmailAndPassword(auth, email, password);
const idToken = await getIdToken(userCred.user);
// API エンドポイントへトークン送信
await fetch("/api/sessionLogin", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ idToken }),
});
// ログイン後はトップページへリダイレクト
window.location.href = "/";
};
return (
<form onSubmit={handleLogin}>
<input type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="email" required />
<input type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder="password" required />
<button type="submit">ログイン</button>
</form>
);
}
3.4 API でセッション Cookie を設定
Next.js の API ルート pages/api/sessionLogin.ts を作成します。
ここでは firebase-admin を使って ID トークンを検証し、セッション ID を生成して Cookie に保存します。
npm install firebase-admin cookie
// pages/api/sessionLogin.ts
import type { NextApiRequest, NextApiResponse } from "next";
import admin from "firebase-admin";
import cookie from "cookie";
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.applicationDefault(),
});
}
// セッションの有効期間(例: 7 日)
const SESSION_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; // 秒
export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method !== "POST") {
res.status(405).end();
return;
}
const { idToken } = req.body;
if (!idToken) {
res.status(400).json({ error: "idToken is required" });
return;
}
try {
// ID トークンを検証し、カスタムセッションを作成
const decoded = await admin.auth().verifyIdToken(idToken);
const sessionCookie = await admin.auth().createSessionCookie(idToken, {
expiresIn: SESSION_COOKIE_MAX_AGE * 1000,
});
// HTTP Only Cookie に保存
const serialized = cookie.serialize("session", sessionCookie, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: SESSION_COOKIE_MAX_AGE,
path: "/",
sameSite: "lax",
});
res.setHeader("Set-Cookie", serialized);
res.status(200).json({ status: "ok" });
} catch (error) {
console.error("Session creation error:", error);
res.status(401).json({ error: "Unauthorized" });
}
};
3.5 サーバーサイドでユーザー情報を取得
pages/_middleware.ts(Next.js 12 以前)や middleware.ts(Next.js 13+)で Cookie を検証し、req.user に情報を付与します。
// middleware.ts (Next.js 13)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import admin from "firebase-admin";
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.applicationDefault(),
});
}
export async function middleware(req: NextRequest) {
const sessionCookie = req.cookies.get("session")?.value;
if (!sessionCookie) {
return NextResponse.next();
}
try {
const decoded = await admin.auth().verifySessionCookie(sessionCookie, true);
// カスタムヘッダーにユーザー情報を埋め込む例
const res = NextResponse.next();
res.headers.set("x-user-id", decoded.uid);
return res;
} catch {
// 無効なセッションは削除
const res = NextResponse.redirect(new URL("/login", req.url));
res.headers.set("Set-Cookie", "session=; Max-Age=0; Path=/");
return res;
}
}
getServerSideProps からはヘッダー x-user-id を読むことで、認証済みユーザーだけのページを簡単に作れます。
4. まとめ
- Firebase Authentication は認証トークンの取得を簡素化し、外部プロバイダーも統一的に扱える。
- セッション管理 はサーバー側で HTTP Only Cookie を使い、XSS に強い構成を取るのがベストプラクティス。
- Next.js と firebase-admin の組み合わせで、SSR でもシームレスにユーザー情報を取得でき、開発者体験が向上する。
実際に手を動かすと、認証フローとセッション管理がどのように連携するかが体感できるはずです。ぜひ自分のプロジェクトに取り入れてみてください。