👀

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

2021/05/24に公開

ローカル環境での開発などではFirebaseのコンソールから作成・ダウンロードした秘密鍵(JSONファイル)をルートなどに配置して以下のように読み込んで秘密鍵を使える。。

const admin = require("firebase-admin");
    if (!admin.apps.length) {
      const serviceAccount = require("path/to/serviceAccountKey.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();
    }

しかしこの秘密鍵は当然公開リポジトリ内に入れられないし、プライベートのリポジトリでも危険だと感じる。そもそもGitコミットにこういった環境変数系を含めるのは気持ち悪いしやはり危険なため、GitHubへのpush経由でデプロイされるVercelには(.gitignoreに入れてコミットに含まれないようにしてるので)アップロードすることができなかった。

だからこそ環境変数というものがあり、next.jsのスターターなどにもはじめからdotenvが入っているのだが、この秘密鍵のJSONファイルをどのように環境変数に変換していいか分からなかった。

(Vercel CLI を使って秘密鍵をGitHub経由させずにアップロードしようとしたが、そんな方法はないようだったし何より上記とやってることは変わらないナンセンスで危険な方法だと思います‥。)

結論

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

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

firebase.tsx
import flamelink from "flamelink/app";
import "flamelink/cf/content";
import "flamelink/cf/storage";

  let firebaseApp;

  if (typeof window === "undefined") {
    console.log('run on server-side.')
    const admin = require("firebase-admin");
    if (!admin.apps.length) {
      firebaseApp = admin.initializeApp({
                                            // require()で読み込むんじゃなくて
        credential: admin.credential.cert({ // cert()の中に直接JSON形式で代入
          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')
    const firebase = require("firebase/app");
    require("firebase/firestore");
    require("firebase/storage");
    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 const app = flamelink({ firebaseApp, dbType: "cf" });

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

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

Vercel側に環境変数を設定

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

参考

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