【Firebase】Next.jsのアプリでAdmin SDKを使って指定したメールアドレスのユーザーにカスタムクレイムを付与する方法

firebase-prograshi(プロぐらし)-kv Firebase
記事内に広告が含まれていることがあります。

ここでは、Firebase Admin SDKを使って、指定したメールアドレスを持つユーザーにカスタムクレイムを付与する手順をまとめています。

秘密鍵のダウンロード

Admin SDKは、通常のFirebase SDK(フロントエンド用)とは異なり、サーバー側でフルアクセス権限を持つため、秘密鍵の生成が必要です。

Firebaseコンソールの対象のアプリに入り、「プロジェクトの概要 > プロジェクトの設定」へと進みます。


「サービスアカウント」タグをクリックし、「新しい秘密鍵を生成」をクリックします。


「キーを生成」をクリックします。


JSONファイルがダウンロードされます。


ファイルのリネーム(.serviceAccountKey.json)

ダウンロードしたファイル名を「.serviceAccountKey.json」とし、プロジェクトのルートディレクトリ直下に配置します。

注意点

秘密鍵が記載されたファイルは全権限を許可してしまう非常に重要なファイルです。

.gitignoreとfirebase.jsonのignoreに記述して、絶対にアップロードされないようにします。


.gitignoreに追記

また、.gitignoreに以下の追記します。

# Firebase Secret Key
.serviceAccountKey.json


firebase.jsonに追記

firebaseの各サービスをデプロイしたときに、Google Cloud 上のソースリポジトリにアップロードされないよう、firebase.jsonの「ignore」にも追記しておきます。

{
  "firestore": {
    "database": "(default)",
    "location": "asia-northeast1",
    "rules": "firestore.rules",
    "indexes": "firestore.indexes.json"
  },
  "hosting": {
    "public": "out",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**",
      ".serviceAccountKey.json",
      "scripts/**" 
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
  "functions": [
    {
      "source": "functions",
      "codebase": "default",
      "disallowLegacyRuntimeConfig": true,
      "ignore": [
        "node_modules",
        "**/.*",
        ".git",
        ".gitignore",
        "firebase-debug.log",
        "firebase-debug.*.log",
        "*.local",
        ".serviceAccountKey.json"
      ],
      "predeploy": [
        "npm --prefix \"$RESOURCE_DIR\" run build"
      ]
    }
  ]
}


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

今回の処理を実行するためには「firebase-admin」「tsx」のパッケージが必要になります。

実行環境はローカルの開発環境にとどめるので、–save-devオプションをつけます。

npm install --save-dev firebase-admin tsx


実行用ファイルの作成

カスタムクレイム付与用のファイル「scripts/set-admin-claim.ts」を作成します。

// scripts/set-admin-claim.ts
//
// 指定したメールアドレスを持つユーザーに role:admin カスタムクレームを付与する
// ローカル実行専用スクリプト。Firebase Hosting には含めないこと。
//
// 【前提条件】
//   firebase login 済みであること
//   (未ログインの場合: npx firebase login)
//
// 【インストール】
//   npm install --save-dev firebase-admin tsx
//
// 【実行方法】
//   付与:
//     npx tsx scripts/set-admin-claim.ts user@example.com
//
//   複数ユーザーに一括付与:
//     npx tsx scripts/set-admin-claim.ts user1@example.com user2@example.com
//
//   確認:
//     npx tsx scripts/set-admin-claim.ts --check user@example.com
//
//   取り消し(role を user に戻す):
//     npx tsx scripts/set-admin-claim.ts --revoke user@example.com

import { initializeApp, getApps } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';

// ─── Admin SDK 初期化 ────────────────────────────────────────────────────────
// firebase login の認証情報(Application Default Credentials)を自動使用する

function initAdmin() {
  if (getApps().length > 0) return;
  initializeApp();
}

// ─── カスタムクレーム操作 ────────────────────────────────────────────────────

async function setAdminClaim(email: string): Promise<void> {
  const user = await getAuth().getUserByEmail(email).catch(() => null);
  if (!user) {
    console.warn(`  ⚠️  ユーザーが見つかりません: ${email}`);
    return;
  }

  const current = user.customClaims ?? {};
  if (current.role === 'admin') {
    console.log(`  ℹ️  既に role:admin です: ${email} (uid: ${user.uid})`);
    return;
  }

  await getAuth().setCustomUserClaims(user.uid, { ...current, role: 'admin' });
  console.log(`  ✅ role:admin を付与しました: ${email} (uid: ${user.uid})`);
}

async function checkClaim(email: string): Promise<void> {
  const user = await getAuth().getUserByEmail(email).catch(() => null);
  if (!user) {
    console.warn(`  ⚠️  ユーザーが見つかりません: ${email}`);
    return;
  }

  console.log(`  📋 ${email} (uid: ${user.uid})`);
  console.log(`     customClaims:`, JSON.stringify(user.customClaims ?? {}, null, 2));
}

async function revokeAdminClaim(email: string): Promise<void> {
  const user = await getAuth().getUserByEmail(email).catch(() => null);
  if (!user) {
    console.warn(`  ⚠️  ユーザーが見つかりません: ${email}`);
    return;
  }

  const { role: _removed, ...rest } = user.customClaims ?? {};
  await getAuth().setCustomUserClaims(user.uid, { ...rest, role: 'user' });
  console.log(`  🔄 role を user に戻しました: ${email} (uid: ${user.uid})`);
}

// ─── エントリーポイント ──────────────────────────────────────────────────────

async function main() {
  const args = process.argv.slice(2);

  if (args.length === 0) {
    console.error(
      '使い方:\n' +
      '  npx tsx scripts/set-admin-claim.ts <email> [email2 ...]\n' +
      '  npx tsx scripts/set-admin-claim.ts --check <email>\n' +
      '  npx tsx scripts/set-admin-claim.ts --revoke <email>'
    );
    process.exit(1);
  }

  initAdmin();

  const [mode, ...rest] = args;

  if (mode === '--check') {
    console.log('🔍 カスタムクレーム確認:');
    for (const email of rest) await checkClaim(email);
    return;
  }

  if (mode === '--revoke') {
    console.log('🔄 role:admin を削除します:');
    for (const email of rest) await revokeAdminClaim(email);
    console.log('\n⚠️  次回ログイン時にトークンが更新されます。');
    return;
  }

  // デフォルト: 付与
  console.log('🚀 role:admin カスタムクレームを付与します:');
  for (const email of args) await setAdminClaim(email);

  console.log(
    '\n⚠️  次回ログイン、またはクライアント側で以下を実行するまで有効になりません:\n' +
    '   await auth.currentUser?.getIdToken(/* forceRefresh */ true);'
  );
}

main().catch((err) => {
  console.error('❌ エラー:', err.message ?? err);
  process.exit(1);
});


firebase cliで実行する

これで準備が整ったので、firebase cliにログインして、スクリプトを実行します。

# Firebase CLIでログイン(未ログインの場合のみ)
npx firebase login

# スクリプトを実行
npx tsx scripts/set-admin-claim.ts user@example.com


以下のように表示され、カスタムクレイムの付与が完了です。

> npx tsx scripts/set-admin-claim.ts test@gmail.com
🚀 role:admin カスタムクレームを付与します:
  ✅ role:admin を付与しました: test@gmail.com (uid: xxxxxxxxxxxxxxxx)

⚠️  次回ログイン、またはクライアント側で以下を実行するまで有効になりません:
   await auth.currentUser?.getIdToken(/* forceRefresh */ true);
タイトルとURLをコピーしました