Firebase Authenticationを使ってアプリを開発していると必ず直面するのが、「特定のユーザーにだけ管理画面を見せたい」という権限管理の壁です。
しかし、Firebaseの標準機能には「管理者フラグ」のような項目は存在しません。
そこで活用すべきなのがカスタムクレイム(Custom Claims)です。これは、ユーザーの身分証(IDトークン)に「admin:true」というデータを付与する機能で、データベースへのアクセスを最小限に抑えつつ、安全なアクセス制限を実現できます。
本記事では、カスタムクレイムの基礎知識から、Next.js(Admin SDK)を用いた具体的な実装手順までを解説しています。
セキュリティを一段上のレベルへ引き上げる準備を始めましょう。
- カスタムクレイム(Custom Claims)とは何か?
- 権限を設定する大まかな流れ
- 実装方法
- 【STEP0】Admin SDKの導入(firebase-admin.tsも)
- 【STEP1】特定のユーザーに管理者権限を付与するための関数を作成する(firebase-admin.ts)
- 【STEP2】ServerActionsにカスタムクレイムを付与するサーバー関数を作成する(actions.ts)
- 【STEP3】一時的な権限付与用のボタンを作成する(temp/page.tsx)
- 【STEP4】ページへのアクセス管理をするコンポーネントを作成する
- 【STEP5】保護したいページ(内容)をAdminGuardタグで囲む
- 【STEP6】ログインしてボタンをクリックする
- 【STEP7】ボタンとmakeMeAdminを削除する
- 【STEP8】ログアウト・再ログインして対象ページを開く
カスタムクレイム(Custom Claims)とは何か?
カスタムクレイムとは、ユーザーのIDトークン(身分証)に独自の項目を書き込める機能のことです。です。
通常、FirebaseのIDトークンには「名前」「メールアドレス」「ユーザーID」といった基本情報しか載っていません。そこに、「この人は管理者(admin)です」という情報を公式に付与します。
Firebaseのサーバー(Admin SDK)を介してのみこの情報を付与することができます。
高いセキュリティ
ユーザーが自分のブラウザ上で勝手に「自分は管理者だ」と書き換えることはできない仕組みになっているため、非常に安全です。
通信コストを抑える
また、ユーザーが管理者かどうかを確認するために毎回データベース(Firestoreなど)を読みに行くと、通信の手間(オーバーヘッド)や料金がかかります。
カスタムクレイムを使えば、ユーザーが持っているID自体に admin: true と書いてあるため、データベースを見に行かなくても、その場で即座に権限を確認できるようになります。
権限を設定する大まかな流れ
指定したユーザーに特定の権限(情報)を付与する大まかな流れは次の3STEPです。
- 付与(サーバー側): Node.jsなどを使って「このユーザーを管理者にする」という設定を送る。
- 反映(クライアント側): ユーザーがログインし直すと、身分証(トークン)の中に設定が含まれる。
- 確認(セキュリティルール/コード): 指定したデータがある人だけアクセスを許可する。
ログイン中の特定のユーザーに権限を付与するためには、ページなどに設定したボタンをクリックしてカスタムクレイムでadmin:trueを付与する関数を実行する必要があります。
実行後は、使用したボタンと権限付与のための関数を削除します。
実装方法
【STEP0】Admin SDKの導入(firebase-admin.tsも)
セキュリティ上、クライアント(ブラウザ)側から自分に管理者権限を付けることはできません。必ずFirebase Admin SDKを使ったサーバーサイドの処理が必要です。
Admin SDKの使い方や初期導入方法については下記をご参考ください。
【STEP1】特定のユーザーに管理者権限を付与するための関数を作成する(firebase-admin.ts)
libディレクトリのfirebase-admin.tsに以下の情報を書き込みます。
import { getAuth } from 'firebase-admin/auth';
//admin カスタマークレイムの付与
export const setAdminRole = async (uid: string) => {
// 指定したUIDのユーザーに { admin: true } というクレイムを付与
await getAuth().setCustomUserClaims(uid, { admin: true });
return { message: "管理者権限を付与しました。再ログイン後に反映されます。" };
};import * as admin from "firebase-admin";を使っている場合は以下のように記述します。
import * as admin from "firebase-admin";
export const setAdminRole = async (uid: string) => {
// admin.auth() を通じてアクセスできる
await admin.auth().setCustomUserClaims(uid, { admin: true });
};【STEP2】ServerActionsにカスタムクレイムを付与するサーバー関数を作成する(actions.ts)
// app/admin/user-list/actions.ts
"use server";
// setAdminRole をインポートする
import { setAdminRole } from "@/lib/firebase-admin";
import { revalidatePath } from "next/cache";
export async function makeMeAdmin(uid: string) {
try {
// lib/firebase-admin.ts で定義した共通関数を呼び出す
await setAdminRole(uid);
console.log(`User ${uid} is now an admin`);
revalidatePath("/admin/user-list");
return { success: true, message: "管理者権限を付与しました。再ログインしてください。" };
} catch (error) {
console.error(error);
return { success: false, message: "エラーが発生しました。" };
}
}【STEP3】一時的な権限付与用のボタンを作成する(temp/page.tsx)
初回のみログイン中のユーザーに権限を付与するボタンがあるページを作成します。
ここでは、tempというディレクトリの中にpage.tsxを作成します。
"use client";
import { auth } from "@/lib/firebase"; // クライアント用Auth
import { makeMeAdmin } from "@/app/admin/user-list/actions";
export default function AdminSetupPage() {
const handleAdminUpgrade = async () => {
// 1. 現在ログインしているユーザーを取得
const user = auth.currentUser;
if (!user) {
alert("ログインしてから実行してください");
return;
}
// 2. Server Actionを実行
const result = await makeMeAdmin(user.uid);
if (result.success) {
alert(result.message);
alert("実行後はこのボタンを削除してください");
} else {
alert("失敗しました: " + result.message);
}
};
return (
<div className="p-10">
<h1 className="mb-4">管理者権限付与ツール</h1>
<button
onClick={handleAdminUpgrade}
className="bg-blue-600 text-white px-4 py-2 rounded shadow-md"
>
ログイン中の自分を管理者にする
</button>
</div>
);
}
このボタンは初回のみ使うものです。一度実行し、ログイン中のユーザーにカスタムクレイムadmin:trueを付与したら、削除します。
※削除しないとこのページにアクセスした人が誰でもボタンを押せてしまします。
【STEP4】ページへのアクセス管理をするコンポーネントを作成する
アクセス管理をして保護したいページ(page.tsx)があるディレクトリにコンポーネントAdminGuard.tsxを作成します。
"use client";
import { useEffect, useState } from "react";
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "@/lib/firebase"; // 上で作った auth をインポート
import { useRouter } from "next/navigation";
export default function AdminGuard({ children }: { children: React.ReactNode }) {
const router = useRouter();
const [loading, setLoading] = useState(true);
useEffect(() => {
// ログイン状態を監視
const unsubscribe = onAuthStateChanged(auth, async (user) => {
try {
if (!user) {
router.push("/"); // 未ログインならトップページに飛ばす
return;
}
// カスタムクレイムを確認
const idTokenResult = await user.getIdTokenResult();
if (idTokenResult.claims.admin) {
console.log("✅ 管理者確認済み");
setLoading(false); // 管理者なら表示許可
} else {
alert("管理者権限がありません");
router.push("/"); // 一般ページへ
}
} catch (error) {
console.error("認証チェックエラー:", error);
}
});
return () => unsubscribe();
}, [router]);
if (loading) return <p>権限を確認中...</p>;
return <>{children}</>;
}
これで、管理者としてログインしている場合のみreturn <>{children}</>;を実行します。
{children}は対象のコンポーネント(ここではAdminGuardタグ)で囲んだ中身のタグが入ります。
【STEP5】保護したいページ(内容)をAdminGuardタグで囲む
今回の場合、admin/user-list/page.tsxを管理者にのみ表示させます。
このため、AdminGuardをインポートして、タグで囲みます。
import AdminGuard from "./AdminGuard";
export default async function AdminUserListPage() {
const users = await getUsers();
return (
<AdminGuard>
元々のタグ
</AdminGuard>
);
}
以上でコードの記述は完了です。
【STEP6】ログインしてボタンをクリックする
管理者権限を付与したいアカウントでログインし、先ほど作成した/tempページにアクセスして「ログイン中の自分を管理者にする」ボタンをクリックします。


【STEP7】ボタンとmakeMeAdminを削除する
管理者権限の付与が終わったら、ボタンを使わないので「temp/page.tsx」をディレクトリごと削除します。
また権限付与するためにServerActionsに作成した関数 makeMeAdminも不要なので削除します。
【STEP8】ログアウト・再ログインして対象ページを開く
ログアウト・再ログインして対象ページを開き、正しくアクセスできるか確認します。

また、他のアカウントでログインしてアクセスした場合に、AdminGuard.tsxで指定したようにトップページにリダイレクトするか確認します。
以上で設定は完了です。
お疲れさまでした。


