💨

【Firebase】メールアドレス変更時にemailVerifiedがfalseにならない問題の解決策

2024/06/16に公開

はじめに
方言を話すおしゃべり猫型ロボット「ミーア」を開発中。
https://mia-cat.com/

アプリでのユーザー認証はFirebaseを利用しているが、一度Firebaaseでユーザー認証を済ませた後にメールアドレスをユーザーが変更したい場合に、メール認証をしなくてもユーザーがメールアドレスを変更できてしまうエラーが発生していたので、今回はこちらの解消を試みる。

メールアドレス変更時はverifyBeforeUpdateEmail()メソッドを利用

Firebaseのドキュメントに記載のように、ユーザーのメールアドレス変更時は、FirebaseAuth.instance.currentUser.verifyBeforeUpdateEmail() メソッドを利用して、新しいメールアドレスを引数にとることで、新しいメールアドレスにメール認証を送ることができる。

https://firebase.google.com/docs/reference/js/v8/firebase.User#verifybeforeupdateemail

この場合、ユーザーが新しいメールアドレスに送られてきたメール認証のリンクをクリックしたタイミングで、Firebase Authenticationに登録されているユーザーのメールアドレスが古いメールアドレスから新しいメールアドレスに切り替わる。

そして、メール認証したかどうかはFirebaseAuth.instance.currentUser.emailVerified を用いて判定できる。

user.emailVerifiedはメールアドレス変更時には役に立たない

初回メール登録時には emailVerified はもちろんメール登録していない状態では false なので、メール認証したかどうかの判定として emailVerified は有用だが、初回メール登録後にメールアドレスを変更する場合には、古いメールアドレスの emailVerified true が引き継がれてしまうために、新しいメールアドレス認証をクリックしなくても emailVerified true で通過してしまう問題がある。

await FirebaseAuth.instance.currentUser.reload() を手前に行ったとしてもこのエラーは解消できない。

つまり、下記コードでは、if (user != null && user.emailVerified) をtrueですり抜けてしまう。

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';

class MailCheckScreen extends StatefulWidget {
  final String email;

  const MailCheckScreen({super.key, required this.email});

  
  _MailCheckScreenState createState() => _MailCheckScreenState();
}

class _MailCheckScreenState extends State<MailCheckScreen> {
  final TextEditingController _authCodeController = TextEditingController();
  final _auth = FirebaseAuth.instance;
  bool isLoading = false;

  void _checkMail() async {
    setState(() {
      isLoading = true;
    });
    // 現在のユーザーを取得します
    User? user = _auth.currentUser;

    if (user != null) {
      // ユーザーの最新の情報を取得するためにリロードします
      await user.reload();
      user = _auth.currentUser;

      if (user != null && user.emailVerified) {
        setState(() {
          isLoading = false;
        });
        // メール認証が完了している場合
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => const LoginScreen(),
          ),
        );
      } else {
        // メール認証がまだ完了していない場合
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text("メールが未確認です。メールを確認してください。"),
          ),
        );
      }
      setState(() {
        isLoading = false;
      });
    }
  }

  
  Widget build(BuildContext context) {
    return BaseContainer();
  }
}

というわけで、別の方法で新しいメールアドレスを認証したかどうかを検出できるようにする必要がある。

メールアドレス変更用のチェッククラスを用意

メールアドレス変更時に、新しいメールアドレスが認証されているかどうかをチェックするために、widget.emailを利用してメールアドレスの一致を確認する。

新しいメールに送られたメール認証をユーザーがしていない段階ではwidget.emailは新しいメールアドレスだが、user.emailは古いメールアドレスのまま(firebase authenticationでメールアドレスが更新されない)なので、メール認証を完了できないようになる。

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';

class MailCheckScreen extends StatefulWidget {
  final String email;

  const MailCheckScreen({super.key, required this.email});

  
  _MailCheckScreenState createState() => _MailCheckScreenState();
}

class _MailCheckScreenState extends State<MailCheckScreen> {
  final TextEditingController _authCodeController = TextEditingController();
  final _auth = FirebaseAuth.instance;
  bool isLoading = false;

  void _checkMail() async {
    setState(() {
      isLoading = true;
    });
    // 現在のユーザーを取得します
    User? user = _auth.currentUser;

    if (user != null) {
      // ユーザーの最新の情報を取得するためにリロードします
      await user.reload();
      user = _auth.currentUser;

      if (user != null && user.email == widget.email && user.emailVerified) {
        setState(() {
          isLoading = false;
        });
        // メール認証が完了している場合
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => const LoginScreen(),
          ),
        );
      } else {
        // メール認証がまだ完了していない場合
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text("メールが未確認です。メールを確認してください。"),
          ),
        );
      }
      setState(() {
        isLoading = false;
      });
    }
  }

  
  Widget build(BuildContext context) {
    return BaseContainer();
  }
}

実際にログを見ると、、、

続きは、こちらで記載しています。
https://kazulog.fun/dev/firebase-mail-change-emailverified-error/

Discussion