Firebaseの関連するユーザデータ削除を簡単に実現してみる【Authentication,Firestore,Functions】


前置き

Firebase Authenticationで、クライアント側から簡単にユーザ登録できるんだから、削除も簡単でしょうと思ってた愚か者のお話です。

ログイン中のユーザインスタンスからdeleteメソッドを叩けばユーザ削除も簡単だよね!やってみよう!

mAuth.getCurrentUser().delete().addOnCompleteListener(task -> {
    if (task.isSuccessful()) {
        Log.d("DEBUG","Successful to delete user.");
    } else {
        Log.e("DEBUG", "Failed to delete user.", task.getException());
    }
});
2021-03-13 11:04:38.569 6841-6841/com.crabsan.anshare.debug E/DEBUG: Failed to delete user.
    com.google.firebase.auth.FirebaseAuthRecentLoginRequiredException: This operation is sensitive and requires recent authentication. Log in again before retrying this request.
        at com.google.android.gms.internal.firebase-auth-api.zztt.zza(com.google.firebase:firebase-auth@@20.0.2:22)
        at com.google.android.gms.internal.firebase-auth-api.zzvb.zza(com.google.firebase:firebase-auth@@20.0.2:9)
        at com.google.android.gms.internal.firebase-auth-api.zzvc.zzk(com.google.firebase:firebase-auth@@20.0.2:1)

上記コードだとエラーに。ログを読むと、ユーザ削除等のセキュリティ上重要な操作には再認証が必要とのこと。
再度ドキュメントを読み直してみると、がっつり書いてましたw

ログ通りの実装に変更するにはSSO毎に別途トークンの取得処理が必要のため、複雑なロジックとなりそうです。
また、その他下記理由から別の方法を取りました。

  • ユーザの再認証(トークン取得等)をすれば実現可能だが、例外処理のパターンが増えクライアント側の処理が複雑になる
  • Authenticationで管理しているユーザ削除と同時に、Firestore(DB)に保存しているユーザデータも削除したい
  • ユーザ削除した履歴も同時に残したい

この記事では、これらを実現するためにしたことをまとめています。

結論

Firebase AuthenticationやFirestoreに登録されたユーザデータを一括で削除するため、下記構成にしてみました。

こちらの構成にすることで以下メリットがありました。

  • クライアント側でFirebase Authenticationの再認証が不要
  • クライアント側の例外処理が簡易(ここが1番のメリット
  • Backgroundで全ユーザ情報を削除できるため、クライアント側の負担減
  • ユーザ削除履歴も処理の流れで生成できる

① 削除するUIDをFirestoreに登録


クライアント側はFirestoreに削除するユーザIDと削除日を書き込む。
これだけです!!
関連データの削除等もFirebase Cloud Functionsに委譲しており、簡単に実装可能です。

// 削除履歴データ生成
final Map<String, Object> deleteData = new HashMap<>();
deleteData.put("uid", uid);
deleteData.put("ctAt", FieldValue.serverTimestamp());

// Firestoreにデータ登録
mFirestore
        .collection('deleted_users')
        .add(deleteData)
        .addOnCompleteListener(mThreadExecutor, task -> {
            if (task.isSuccessful()) {
                // ユーザ削除完了後の処理
            } else {
                // ユーザ削除失敗のため、UIへ失敗通知
            }
        });
{
    uid: "xxx"
    createdAt: 1615600018000
}

注意点としては、Firestoreのセキュリティールールをしっかり導入しておくこと。
Firestoreの特定コレクションにデータ追加さえすればユーザ削除ができてしまうので、悪用される懸念は当然あります。
自分は以下ルールを設定し、リクエストしたユーザしか自身のデータを追加できない制約を与えています。

  • 登録するドキュメントのuidとリクエストのuidが同じであること
  • ドキュメント内のデータ数が同じであること
  • 登録日時がサーバ時刻と極端に離れていないこと

②③ ドキュメント登録をトリガーに、Function経由でAuthenticationからユーザ削除

Firestore Functionsに関数を登録します。
①のドキュメント追加(onCreate)をトリガーに、Authenticationのユーザ情報を削除する流れです。
とても簡単ですね。

exports.deleteUser = functions
    .region('asia-northeast1')
    .firestore
    .document('deleted_users/{docId}')
    .onCreate(async (snap, context) => {
      const deleteDocument = snap.data();
      const uid = deleteDocument.uid;

      await auth.deleteUser(uid);
})

④⑤ Authenticationのユーザ削除をトリガーに、Function経由でユーザ情報削除


②③でAuthenticationのユーザが削除された事をトリガーに、Firestore(DB)に保存されているユーザ情報をFunctionsで全削除します。
こちらは、Firebase側が提供している Delete User Data Extensionsを利用します。

Delete User Data Extensionsを利用することで、Authenticationのユーザが削除されたをトリガーに、UIDに紐づく指定したサブコレクションをキレイに削除可能です。
また、下図のようにUI上で削除対象を登録することができます。
②③のような、Functionsのコードを自前で管理する必要もないので、オススメです!

まとめ

Firebase Authentication、Firestoreのユーザデータ削除方法をまとめてみました。
個人Androidアプリ開発で困った箇所なので、同じ悩みを持たれている方の参考になれば嬉しいです!

※ 内容に誤りやこの構成マズイんじゃない?というご意見あれば、コメントやTwitterでご連絡いただけるととても助かります