Arshesのアカウント認証について
はじめに
趣味でArshesというiOSアプリを開発している。
テーマは「UGCプラットフォーム開発を一人で小さく真面目に全部やる」ということにしており、 仕事で向き合う必要がでてきたクラウドサービスやデータベースなどの素振りを兼ねている。
私は元々Unityを使った開発をしていたので、特にサーバー側のことは勝手がわからないので、ひとつずつやる良い機会になっている。
Firebase Authentication
UGCプラットフォームなのでアカウントの認証が必要である。 アカウント認証は外部に押し付けるのが楽であり、仕事でも使っているFirebase Authenticationを使うことにした。 Firebaseを使う場合、全部Firebaseの上でやってしまうか、あるいはFirebaseを使う場所を限定するかの二択があり、中途半端に使ったり使わなかったりすると大変である。今回はAuthentication、Analytics、Crashlyticsに絞ることにした(後々でCloud Functions for Firebaseを使うかもしれないが、とにかく薄くする)。
MFAを自前で実装するのは面倒くさいので認証方法はGoogle認証に絞る。後になって外部テスト用に審査に提出する際に、審査員にアカウント情報を渡す必要がでてきたので、Appleアカウントでも認証できるようにした。
— shivaduke (@shiva_duke28) 2025年12月23日
Hasura Cloud
UGCなのでユーザーが生成したデータを保存するデータベースが必要になる。Firebaseと同様に仕事で使っているHasura Cloudを使うことにした。 Hasura Cloudというのは、dbと接続するといい感じにスケールするGraphQLサーバを立ててくれるサービスである。 dbは別途立てる必要があり、Google Cloud SQL、Herokuなどの候補があるが、無料枠が結構使えてHasura Cloudも推してそうな雰囲気のNeonを採用した。dbはPostgreSQLのだけど、dbのスキーマも含めてHasuraのコンソールやHasura CLIを使って操作するのが一般的である。
2つのusersテーブル
ユーザーが作ったコンテンツはHasuraで管理する。「自分のコンテンツ」を表示することなどを考えると、当然Hasuraにもusersテーブルが欲しくなる。 すると、「FirebaseとHasuraの両方に”usersテーブル”が作られるはずだが、どうやって整合させるのか?」ということが気になってくる。
まず思いつくのは、Firebaseでの認証イベントをフックしてHasuraにも変更を加えるということである。 FirebaseをBlazeプラン(従量課金)にしてFirebase Authentication with Identity Platformにグレードアップすれば、Cloud Functions for Firebaseでブロッキング関数が使えるようになる。
参考:ブロッキング関数を使用して Firebase Authentication を拡張する
ブロッキング関数は、Firebase認証のsignupやsigninを文字通りブロックする処理を書くことができる仕組みで、JWTに手を入れたりもできるし、 ここでHasuraに対してadmin権限でmutationを投げることもできる。 こうすれば、アカウント認証を行えば勝手にHasuraにもレコードが作られることは保証できる。
では、アカウント削除時はどうか? HasuraとFirebaseどちらから先に削除するべきだろうか。 「アカウント削除」でユーザーがやりたいことは、「自分が作ったコンテンツが(少なくとも見た目上は)削除され、自身の情報が消える」ことである。 Cloud Functionsではアカウント削除イベントをブロックすることはできず、アカウント削除後に行う処理だけが書ける。 つまり、Hasura Cloud側で障害が起きると「Firebaseからはアカウント削除ができたがHasuraにはデータが残っている」可能性がでてくる(この前のCloudflare障害とかでHasura Cloudは落ちてた)。 こうなると、ユーザーから「私のデータを消してください」というお問い合わせが来るはずだが、Firebaseからはデータが消えているので、どれが該当するユーザーのデータかを知るのが難しそうに思われる。
したがって、削除する時はHasura→Firebaseから削除の順にやる方が良い、ということになる。前職の元同僚に相談すると「アウトボックスパターンでやるのが良いんでは」と助言された。つまり、まずHasuraでusersテーブルからの削除と「Firebaseからの削除」コマンドをアウトボックステーブルに追加するのを同一トランザクションで行う。次に定期実行する処理でアウトボックステーブルを見て、未処理のコマンドがあれば実行(つまり、Firebase Auth Admin APIを使ってFirebaseからユーザーの削除を行う)をする、ということになる。ユーザーを削除するだけでこんなに大変なのかと驚いた。勉強になる。ありがとうございます。
ともかく、ユーザ目線ではFirebaseにデータが残ってるかどうかは本質的ではないので、
- Firebaseで作成
- Hasuraで作成
- Hasuraで削除
- Firebaseで削除
という順番でやるのが良さそうということがわかる。そうすると、アカウント作成時にCloud Functionsを使いつつ削除時はアウトボックスパターンでやるというのは、なんだか非対称で難しく、アカウント管理のロジックが分散するのが難しく感じる。Cloud Functionsのブロッキング関数でdbを”同期”させるのは、いまいちであるという結論になった。
Arshesのアカウントのライフサイクル
ArshesではCloud Functionsもアウトボックスもやらずに、clientが正しい順番でFirebaseとHasuraにリクエストを投げる形にした。
まず、Hasuraでusersテーブルを以下のように作る。

万が一にも(多分ないけど)Firebaseやめたくなったときに主キーを変更するのがめちゃくちゃ大変そうなので user_idとfirebase_uid(Firebase認証上のユーザーのid)は別にした。
クライアント側はアプリを起動すると最初にFirebaseで認証を求める。 Firebaseで認証すると、Firebase uidがわかるので、それを使ってHasuraにqueryを投げる。 userレコードがあれば既存ユーザーだし、無い場合は新規ユーザーなのでアカウント作成フローに入り、 username、display_nameを入力する画面を表示する。
アカウント削除時は逆で、まずHasuraに対してユーザーデータを削除するmutationを投げる。usersテーブルには子テーブルがあり、user_idを外部キーとして参照している。今はデータが少ないので、ON DELETE CASCADEで一緒に削除する。データが増えたら困るという話があるが、困ったときに話題の論理削除などを実装すれば良い、と判断した。
Hasuraからの削除に成功したらクライアントがFirebaseに対してアカウント削除のリクエストを投げる。ここが失敗した場合、Hasuraにはデータが無いがFirebaseにはデータがある状態になるので、新規アカウント作成時と同じ状況になる。実害がないのでこれでええかと思っているが、ちゃんとやりたいなら退会済みユーザのFirebase uidを残しておくとかすると良さそうではある。
セキュリティについて
一般的にサーバーはクライアントが送ってくる情報を信用するべきではない。 クライアントが送ってくるFirebaseのuidは信用していいのか、というのが気になる。 この辺は、HasuraがJWTを使ったパーミッション設定ができるようになっているので問題ない。 Firebaseで認証するとJWTにFirebaseのuidが入る。 クライアントはこれをリクエストヘッダに詰めてHasuraにリクエストを送る。 HasuraではJWTを読むことができるようになっていて、per table、per column、per operation単位でのパーミッション設定にJWTの中身を使える。
これを使って、usersテーブルへのinsert時に「書き込もうとしているFirebase uidがJWTに入っているものと同じでないと失敗する」みたいな制限が表現できる。
終わりに
なんか間違ってること書いてたら教えてくれると大変助かります🙏