*この資料はナレッジワークの社内勉強会で利用したものを公開したものです
問題点tl;dr
electronのrendererプロセスでfirebase-sdkを利用する場合、URLにlocalhostを使うことになるので、firebase-authの承認済みドメインにlocalhostを許可せざるをえないという問題があります
解決策tl;dr
以下のように解決します
- mainプロセスへfirebase sdkを利用する処理を逃がす
- sign inはサーバーを用意し、firebae-adminで
createCustomToken
を使って行い、そのtokenをmainプロセスで利用する - ただし、mainプロセス(=nodeのプロセス)上ではfirebase-authのユーザーのPersistができず、アプリ立ち上げ時に過去に取得した認証情報を取ることができない問題がある。その対処としてelectron用に独自のpersistenceを実装することで問題を解決する(参照実装はこちら)
モチベーション
firebase-authは信頼できるドメイン上でのみ認証フローを通せるような設定として、 承認済みドメイン(Authorized domain)
という設定があります
一般に本番環境で運用するアプリではこの承認済みドメインにlocalhostを追加しない( *要出典12 )のですが、素直にelectronでfirebase-authを使おうとすると、localhostを許可することになってしまいます
electronアプリはmainプロセス・rendererプロセスという2つのプロセスを扱ってアプリケーションを構築します
mainプロセスはNode.jsで動き、ネイティブAPIを利用出来ます
rendererプロセスはChromiumでwebコンテンツ(htmlなど)をレンダリングします
素直にelectronでfirebase sdkを利用する場合、rendererプロセスでlocalhostのコンテンツを表示して、その中で利用する形になるのですが、これはつまり、firebase-authの signInWithPopup
などの関数を利用してsign inするために承認済みドメインにlocalhostを追加する必要が出てしまう、ということになるというわけです
ちなみにfirebaseは基本的にwebやネイティブアプリでの利用をサポートしていますが、一応デスクトップアプリ、例えばCordvaがサポートされていたりするのですが、electronはサポート外なのですね
解決方法(再掲)
以下のように解決します
- mainプロセスへfirebase sdkを利用する処理を逃がす
- sign inはサーバーを用意し、firebae-adminで
createCustomToken
を使って行い、そのtokenをmainプロセスで利用する - ただし、mainプロセス(=nodeのプロセス)上ではfirebase-authのユーザーのPersistができず、アプリ立ち上げ時に過去に取得した認証情報を取ることができない問題がある。その対処としてelectron用に独自のpersistenceを実装することで問題を解決する
mainプロセスへfirebase sdkを利用する処理を逃がす
基本的にfirebase sdkはNodeでもブラウザでも動くように作られています
ただし、firebase-authのsignIn系処理はブラウザで実行することを前提としているので、node上でsignIn系の関数が実行できません
そのため、sign in処理を行うサーバーを用意し、そちらで認証を通してelectron appに戻ってくる、というフローを取ります
こんなシーケンスになります
(参考: rendererプロセスで使う場合はこんな感じになります)
sign inのシーケンスをこのように変更することで、localhostを承認済みドメインに含めずに済みました
実際にfirebase sdkを利用してAPIを叩くときはmainプロセスを経由して実行することになります(面倒ではありますが・・・)
これで全部解決とはいきません
なぜなら、firebase-authはnode上で認証情報を永続化する仕組みになっていないためです(nodeでsignIn系関数使えないんだからそりゃそう)
認証状態の永続性のドキュメントで記載されている
-
firebase.auth.Auth.Persistence
のLOCAL
,SESSION
,NONE
はそれぞれブラウザで言えばlocalStorage
,sessionStorage
,オンメモリ
で保存する実装になっています
例えばこの辺が PersistenceType.LOCAL
の実態
https://github.com/firebase/firebase-js-sdk/blob/firebase%4010.7.1/packages/auth/src/platform_browser/persistence/local_storage.ts#L54
この永続化の仕組みをどうにかする必要があります
さてここでAuth の依存関係のカスタマイズというドキュメントを見ると、firebase-authのpersistenceは、sdkの初期化時にDI出来ることがわかります
// https://firebase.google.com/docs/auth/web/custom-dependencies?hl=ja より引用
import {initializeAuth, browserLocalPersistence} from "firebase/auth";
import {initializeApp} from "firebase/app";
const app = initializeApp({/** Your app config */});
const auth = initializeAuth(app, {
persistence: browserLocalPersistence,
// No popupRedirectResolver defined
});
ここは本来、firebase-auth側で実装済みのpersistenceクラスを渡す想定で、Persistenceのinterfaceも以下のように、 readonly type: 'SESSION' | 'LOCAL' | 'NONE';
のみが定義されたものになっています
// https://github.com/firebase/firebase-js-sdk/blob/537d39982f90aff50519b8c1ad6d58048fb8f244/packages/auth/src/model/public_types.ts#L334-L342 より
export interface Persistence {
/**
* Type of Persistence.
* - 'SESSION' is used for temporary persistence such as `sessionStorage`.
* - 'LOCAL' is used for long term persistence such as `localStorage` or `IndexedDB`.
* - 'NONE' is used for in-memory, or no persistence.
*/
readonly type: 'SESSION' | 'LOCAL' | 'NONE';
}
が、渡している値はJavaScriptのクラスによる実装本体なので、ここに自前のpersistenceを渡してあげれば動かすことが出来ます(!)
ということで、electron向けに再実装してあげて↓のように初期化してあげれば無事永続化が出来るようになります
// https://github.com/sisisin-sandbox/electron-with-firebase/blob/acea70ccc06bf91ba52d5f7c189df5b620b624a3/src/main/firebase/firebase.ts
import { initializeApp } from 'firebase/app';
import { initializeAuth } from 'firebase/auth';
import { electronLocalPersistence } from './_internal/ElectronLocalPersistence';
// ...
export const fbAuth = initializeAuth(fbApp, {
persistence: electronLocalPersistence,
});
以上の手順で、electronでfirebase-authを利用しつつ、承認済みドメインの設定にlocalhostを入れないで済ませることが出来ました
もし参考になればこれ幸い
-
Firebase セキュリティ チェックリスト などを見てもlocalhostを指定してはいけないという明示的な記載がないので、もしかしたらマジでlocalhost入れても良いのかもしれない。もしそうだったら全て徒労ですね ↩
-
とはいえ、localhostを許可しているとkeyをダウンロードして好きに利用できてしまうので、AppCheckなどを通さずにsdkを利用してサービスに対して攻撃する余地が生まれる、といった問題はありそうです ↩