【Flutter×Firebase】Firebaseアカウント削除にあたり最近のログインがないと削除できないため対応したこと

Flutterアプリ

FlutterでFirebaseを使用したアプリをリリースした際にAppleから「登録機能を作るなら削除機能も作ってね」とレビューをいただいたので、削除機能を作りました。

その際に出た下記エラーの解消方法について、備忘録としてここに記載いたします。

This operation is sensitive and requires recent authentication. Log in again before retrying this request.

日本語訳

この操作は機密性が高く、最近の認証が必要です。このリクエストを再試行する前に、再度ログインしてください。

エラーの内容的には、最近ログインしてないと削除のリクエストは投げられないですよ」みたいなメッセージです。

環境

私の環境は以下の通りになります。

Flutter 3.10.4 
Tools • Dart 3.0.3 • DevTools 2.23.1
Android Studio Koala Feature Drop | 2024.1.2

エラーに対する対処法

エラーが起こったシチュエーション

Firebaseのアカウント削除機能を追加し実行ボタンを押すと、下記エラーが返ってきました。

This operation is sensitive and requires recent authentication. Log in again before retrying this request.

公式の記載

Deleting a user

If your user wishes to delete their account from your project, this can be achieved with the delete() method. Since this is a security-sensitive operation, it requires that user must have recently signed-in. You can handle this scenario by catching the error, for example:

日本語訳

ユーザが自分のアカウントをプロジェクトから削除したい場合は、delete() メソッドを使用します。これはセキュリティーを重視した操作なので、ユーザーが最近サインインしている必要があります。このシナリオは、たとえばエラーをキャッチすることで対処できます

Using Firebase Authentication | FlutterFire
This page is archived and might not reflect the latest version of the

削除処理

  • deleteUserAccountという関数を使用して、Firebaseの最近ユーザーを削除処理を実行
  • エラーが返ってきた場合にプリント
    • こちらでエラー発生
Future<void> deleteUserAccount() async {
  try {
// 削除処理
    await FirebaseAuth.instance.currentUser!.delete(); 
  } on FirebaseAuthException catch (e) {
// エラー時のエラーメッセージを出力
    print('error: $e');
  } catch (e) {
    print(e);
  }
}

エラーが出ないケース

  • 直近でログインした場合

この削除処理の確認に伴い、新しいアカウントでログインし、削除したらエラーが表示されることなく、deleteUserAccount関数(アカウント削除処理)が通りました。

エラーメッセージの通り最近のアクセスがあれば問題ないみたいです。

解決方法

該当エラーが出た場合にダイアログを表示しパスワードをユーザーに入力してもらうことで解決できました。

Google,Appleアカウントを紐付けしている場合、別途対応が必要になります。私の場合はメール・パスワードの設定にしています。

Future<void> deleteUserAccount() async {
  try {
    await FirebaseAuth.instance.currentUser!.delete();
  } on FirebaseAuthException catch (e) {
    print('error: $e');
    // パスワードを入力するダイアログを表示
    await _showPasswordDialog(context);
  } catch (e) {
    print(e);
  }
}

上記では、エラー全てでダイアログを表示する関数に飛ばしていますが、下記の参考サイトのように、特定のエラーを読み取り関数に飛ばす方が正解かもしれないため、そこは個人次第かなと思います。

Delete User Accounts in Flutter apps with Firebase Auth
My Apple Store submission failed for a reason — and I want you to be prepared and avoid it

私は、同じエラーでもエラーメッセージが変わるかも?と思いエラーで一旦全部パスワード入力関数に飛ばすようにしました。

_showPasswordDialog関数の中身(パスワード入力用のダイアログ表示関数)

_showPasswordDialog関数では、以下のようなことを実施

  • TextFieldにパスワードを入力してもらう
  • ユーザーがOKボタン押下後、パスワードが入力されていたら再認証用の関数(_reauthenticateUser())に飛ばす
Future<void> _showPasswordDialog(BuildContext parentContext) async {
  final TextEditingController passwordController = TextEditingController();
  print('showPasswordDialog');

  await showDialog(
    context: parentContext,  // Pass the context from the parent widget
    builder: (BuildContext context) {
      return AlertDialog(
        title: const Text('削除するにはパスワードを入力してください'),
        content: TextField(
          controller: passwordController,
          decoration: const InputDecoration(labelText: 'Enter your password'),
          obscureText: true,
        ),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.pop(context);  // Close the dialog
            },
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () {
              final password = passwordController.text;
              if (password.isNotEmpty) {
                Navigator.pop(context);  // Close the dialog
                 _reauthenticateUser(parentContext, password);  // Pass the parent context
              } else {
                ScaffoldMessenger.of(parentContext).showSnackBar(
                  const SnackBar(content: Text('Please enter a password')),
                );
              }
            },
            child: const Text('OK'),
          ),
        ],
      );
    },
  );
}

_reauthenticateUser関数の中身(再認証関数)

  • ログインしているユーザー(currentUser)のパスワード情報と一致しているか確認
  • ユーザーの再認証をawaitを使用し実行

上記のことを実施することでパスワードが一致していれば、削除することができました

reauthenticateWithCredential関数については、Firebaseの公式を参考にしました。

Using Firebase Authentication | FlutterFire
This page is archived and might not reflect the latest version of the
// 再認証用の関数
void _reauthenticateUser(BuildContext context, String password) async {
  User? user = FirebaseAuth.instance.currentUser;

  if (user != null) {
    try {
      String email = user.email!;
      final credential = EmailAuthProvider.credential(email: email, password: password);
      await user.reauthenticateWithCredential(credential);

      _performSensitiveAction(context);

    } catch (e) {
      print('Re-authentication failed: $e');
    }
  }
}

_performSensitiveAction関数の中身(再認証ができた場合にアカウントを削除する)

最後に下記のようにボタンを押した際に、アカウント削除を実行すると、一度目は裏で「最近ログインしていないよ」と出ますが、再認証が通っているため削除が完了いたします。

void _performSensitiveAction(BuildContext context) {
  showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: const Text('Success'),
        content: const Text('パスワードの確認が完了し、アカウントを削除いたしました。'),
        actions: [
          TextButton(
            onPressed: () async {
              await FirebaseAuth.instance.currentUser!.delete();
              Navigator.pop(context);
            },
            child: const Text('OK'),
          ),
        ],
      );
    },
  );
}

最後に

私の場合、上記の実装方法で解決できました。細かい関数の配置やボタンの処理はご自身で調整するのが良いかなと思います。

Flutter×Firebaseはこういったお作法がたくさんあるので私も覚えていきたいと改めて思いました。Flutter開発のどなたかにご参考になれば幸いです。

ではまた。

コメント

タイトルとURLをコピーしました