👀

Vercelに Firebase Admin SDK の秘密鍵の情報を設定する

に公開

ローカル環境での開発では Firebase コンソールから作成・ダウンロードした秘密鍵(myproject-firebase-adminsdk-xxx.json)をプロジェクトのルートなどに配置して以下のように読み込んで秘密鍵を使える(.gitignoreに入れてコミットに含まれないように注意)。

const admin = require("firebase-admin");

if (!admin.apps.length) {
  const serviceAccount = require("path/to/myproject-firebase-adminsdk-xxx.json");
  firebaseApp = admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: process.env.FIREBASE_DATABASE_URL,
    storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
  });
} else {
  firebaseApp = admin.app();
}

この秘密鍵のJSONファイルをどのように環境変数に変換していいか分からなかった。

結論

FSA_PROJECT_ID=myproject
FSA_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nxxx\n-----END PRIVATE KEY-----\n
FSA_CLIENT_EMAIL=firebase-adminsdk-yyy@myproject.iam.gserviceaccount.com

変数はこの3つだけでいいようです‥。ダブルクォーテーションやシングルクォーテーションで囲むとまた挙動が変わってくるかもしれない。またFirebase Authを使うときは追加の環境変数が必要かもしれません。

firebase初期化時の使い方

2025年追記:これ本来 clientApp.ts と nodeApp.ts のように初期化コードを分けないといないような、、。下記のコードだと一度 server-side で初期化された firebaseApp を client-side が使えてしまって非常にまずいことにならないか、、。今回は json -> 環境変数への転用の話がメインなので動作検証までしないですが、下記の初期化方法はよくないと思います..。

firebase.ts
// 追記: import 文へのリプレイス
import * as admin from 'firebase-admin';
import * as firebase from "@firebase/app"; // 最新の firebase では @ が付く方を推奨
import * from '@firebase/firestore'; // ここで getFirestore() しないなら不要では?
import * from '@firebase/storage'; // ここで getStorage() しないなら不要では?

  let firebaseApp;

  if (typeof window === "undefined") {
    console.log('run on server-side.')
    if (!admin.apps.length) {
      firebaseApp = admin.initializeApp({
        // require()で読み込むんじゃなくて cert() の中にJSONを代入
        credential: admin.credential.cert({
          projectId: process.env.FSA_PROJECT_ID,
          privateKey: process.env.FSA_PRIVATE_KEY.replace(/\\n/g, '\n'),
          clientEmail: process.env.FSA_CLIENT_EMAIL,
        }),
        databaseURL: process.env.FIREBASE_DATABASE_URL,
        storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
      });
    } else {
      firebaseApp = admin.app();
    }
  } else {
    console.log('run on client-side')
    if (!firebase.apps.length) {
       firebaseApp = firebase.initializeApp({
        apiKey: process.env.FIREBASE_API_KEY,
        authDomain: process.env.FIREBASE_AUTH_DOMAIN,
        databaseURL: process.env.FIREBASE_DATABASE_URL,
        projectId: process.env.FIREBASE_PROJECT_ID,
        storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
        messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
      });
    } else {
      firebaseApp = firebase.app();
    }
  }

  export firebaseApp;

ポイントはprivateKeyにおいては\nの改行が悪さするようで.replace(/\\n/g, '\n')を付けてやらないといけないこと。

ちなみに上記のコードは SSR を行う Next.js ではサーバーサイドとクライアントサイドの両方で同じコードが走る関係で Admin SDK が必要だったので、それぞれで使うSDKを分岐させる形になっています。

Vercel側に環境変数を設定

これはご存知と思いますが、localで使ってた.envは当然gitignoreされてるので、Vercel(にデプロイするためのGitHubリポジトリ)にpushしても自動で環境変数を設定してはくれません。コンソール(もしくはVercel CLI)の Settings > Environment Variables から手動で1個ずつ設定して終わりです。

数年後に追記

割といいねが多いので数年後に見返したら、当時の導入文とかわかりにくいしちょっと不安な発言も多かったのでリライトしました..。以下、当時の記述への自分ツッコミ。

この秘密鍵は当然公開リポジトリ内に入れられないし、プライベートのリポジトリでも危険だと感じる。

そうだね

そもそもGitコミットにこういった環境変数系を含めるのは気持ち悪い

その気持ち悪いという感覚は大事だ。そういった直感で救われることが多々あった

GitHubへのpush経由でデプロイされるVercelには(.gitignoreに入れてコミットに含まれないようにしてるので)秘密鍵をアップロードすることができなかった。だからこそ環境変数というものがあり、next.jsのスターターなどにもはじめからdotenvが入っているのだが

Next.js や React を使っていたら環境変数って普通に使うと思うのだが、秘密鍵のjsonファイルを見てもそれを環境変数として使う方法がパッと思いつかない状態だったってことか。これは中級者以上は忘れてしまっている感覚かもしれない。たぶんドキュメントやコピペでの開発が中心で、何がどうなってどう動くのか、それを理解してモノを作る力がまだ弱いからだろう(だがそれでいい)。

Vercel CLI を使って秘密鍵をGitHub経由させずにアップロードしようとしたが、そんな方法はないようだったし何より..<中略>..ナンセンスで危険な方法だと思います

その発想にいっちゃう?多分WordPressのファイルをFileZillaやFFFTPでアップロードしたりしてたから、GitHub経由でのCIに慣れてなかったりしたからだろう。なんでナンセンスで危険と思ったかは不明だが、curl などで POST する先のURLを1文字間違えたりして秘密鍵が漏れてしまうからかも。ちなみに環境変数の設定であればもちろん Vercel CLI で vercel env add コマンドで設定できるが、初心者は確かに管理画面からポチポチやった方が \n の改行とかをミスったりしないし安定。
https://vercel.com/docs/cli/env

解決しない場合

お困りでしたら X のDM、もしくは Lancers などからご相談ください。
https://www.lancers.jp/profile/takuya108817

参考

https://qiita.com/takaken/items/7354ca4fd96679eaf09b
https://stackoverflow.com/questions/50299329/node-js-firebase-service-account-private-key-wont-parse

AUTHに関しては以下のIssueが参考になりそう

ただし今回はこの'" "'でprivateKeyを囲むというやり方はうまくいかなかった
https://github.com/vercel/vercel/issues/749#issuecomment-707515089

Discussion