4
Help us understand the problem. What are the problem?

posted at

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
ユーザを削除するページの抜粋PNG.PNG

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

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

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

結論

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

Firebaseユーザ削除機能の全体フロー図.PNG

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

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

① 削除するUIDをFirestoreに登録

ユーザ削除機能の処理1.PNG
クライアント側は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からユーザ削除

ユーザ削除_parts2.PNG

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経由でユーザ情報削除

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

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

delete-user-data.PNG

まとめ

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
4
Help us understand the problem. What are the problem?