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

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

ログイン中のユーザインスタンスから[deleteメソッド](https://firebase.google.com/docs/auth/web/manage-users#delete_a_user)を叩けばユーザ削除も簡単だよね！やってみよう！

```java: Auth.java
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());
    }
});
```

```bash: ログ
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)
```
上記コードだとエラーに。ログを読むと、**ユーザ削除等のセキュリティ上重要な操作には再認証が必要**とのこと。
再度ドキュメントを読み直してみると、がっつり書いてましたｗ
![ユーザを削除するページの抜粋PNG.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/424303/c58ccdaf-205e-340c-d7d7-b40c7438333c.png)

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

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

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

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

![Firebaseユーザ削除機能の全体フロー図.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/424303/62ff71d8-4579-f450-546c-fbc3e31c4051.png)

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

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


# ① 削除するUIDをFirestoreに登録
![ユーザ削除機能の処理1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/424303/93b9b81f-1308-4f39-5ad2-b75713ce3390.png)
クライアント側はFirestoreに削除するユーザIDと削除日を書き込む。
これだけです！！
関連データの削除等もFirebase Cloud Functionsに委譲しており、簡単に実装可能です。

```java: deleteUser.java
// 削除履歴データ生成
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へ失敗通知
            }
        });
```

```json: Firestoreドキュメントイメージ
{
    uid: "xxx"
    createdAt: 1615600018000
}
```

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

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


# ②③ ドキュメント登録をトリガーに、Function経由でAuthenticationからユーザ削除
![ユーザ削除_parts2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/424303/9d0138c3-d949-a4a0-4c85-4a3bb271a432.png)

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

```javascript: Firebase Functions
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/424303/50d607bd-f8b9-c3b0-ce89-be3226cbe51d.png)
②③でAuthenticationのユーザが削除された事をトリガーに、Firestore(DB)に保存されているユーザ情報をFunctionsで全削除します。
こちらは、Firebase側が提供している **[Delete User Data Extensions](https://firebase.google.com/products/extensions/delete-user-data)**を利用します。

https://firebase.google.com/products/extensions/delete-user-data

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

![delete-user-data.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/424303/bd435e55-f28a-7fd1-63c8-b76cda007f31.png)

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

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