【Flutter✖️Firebase】電話番号認証を実装する

今回はFirebaseを用いで電話番号認証機能を実装していきます。

## 前提
下記の設定は行なった前提で進めていきます。
1.FlutterとFirebaseを連携
2.Firebaseコンソールで電話番号認証を有効にする
![スクリーンショット 2022-06-22 9.40.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466417/f251f12c-43fd-90d8-93f5-7b846d275040.png)

## バージョン
Flutter 3.0.0
firebase_core: ^1.17.1
firebase_auth: ^3.3.19

## 1. 各プラットフォームの設定
電話番号認証機能を実装するには各プラットフォームで設定を行う必要があります。
### Android
デバッグ用に署名証明書SHA1を取得する必要があります。
プロジェクトのpathで下記コマンドを実行。

```
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
```
ターミナルからSHA1キーを確認することができるのでコピー。

```
証明書のフィンガプリント:
         SHA1: <ここをコピー>
```
firebaseの「プロジェクトの設定」でプロジェクトと紐づいたAndroidアプリを開き、「SHA証明書フィンガープリント」部分に先ほどコピーしたキーを追加。

![スクリーンショット 2022-06-22 11.29.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466417/29b5fa67-bbd9-da8b-bc0d-38501e8b0b40.png)
### ios
iosではプッシュ通知の設定が必要になります。詳しくは下記の記事が参考になります。

https://zenn.dev/mamushi/articles/flutter_push_notification

### web
登録済みwebアプリのOAuthリダイレクトドメインに、アプリケーションのドメインを追加する必要があります。ドメインの追加には下記の記事が参考になりそうです。

https://sunday-morning.app/posts/2021-05-15-firebase-auth-google-custom-domain


:::note warn
注意；電話番号認証は、実機とWebでのみ使用可能です。
シュミレーターでテストを行いたい場合はfirebaseで別途設定が必要になります。
詳細 → https://firebase.flutter.dev/docs/auth/phone/#testing
:::

## 2.パッケージをインストール
電話番号認証を行う場合は下記２つのパッケージをインストールする必要があります。

```
flutter pub add firebase_core
flutter pub add firebase_auth
```

https://pub.dev/packages/firebase_core

https://pub.dev/packages/firebase_auth

## 3.実装
スマートフォン（ios、Android）とWebでは異なるメソッドを使用する必要があり、今回はiosのみの実装なので`verifyPhoneNumber`メソッドを使用します。

:::note info
webを実装する場合は下記を参考にしてください。
https://pub.dev/documentation/firebase_auth/latest/firebase_auth/FirebaseAuth/signInWithPhoneNumber.html
:::

フロント部分載せると冗長になりそうなので電話番号認証の実装部分のみ解説していきます。電話番号入力用のフォームと登録ボタンを設置し、登録ボタンを押下すると下記メソッドが発火するように実装。引数に入力された電話番号が入るようにしてください。

```dart
  Future<void> verifyPhone(String phone) async {
    await FirebaseAuth.instance.verifyPhoneNumber(
      phoneNumber: '+81$phone',
      verificationCompleted: (PhoneAuthCredential credential) {},
      verificationFailed: (FirebaseAuthException e) {},
      codeSent: (String verificationId, int? resendToken) {},
      codeAutoRetrievalTimeout: (String verificationId) {},
    );
  }
```
詳しく解説していきます。
- **phoneNumber**
国別の番号（日本は+81）と入力された電話番号をいれる。ここで入力された電話番号宛に認証コードが送信されます。

- **verificationCompleted**
Androidの場合はこちらで実装を行います。今回はiosのみなので割愛。

- **verificationFailed**
Firebaseのエラー（電話番号が正しくない等）が発生したときにここでエラーを受け取ることができます。
```dart
await FirebaseAuth.instance.verifyPhoneNumber(
  verificationFailed: (FirebaseAuthException e) {
    if (e.code == 'invalid-phone-number') {
      print('電話番号が正しくありません。');
    }
  },
);
```
- **codeSent**
このハンドラが起動したら`showDialog`等を用いてユーザーの認証コードの入力を待つ必要があります。
```dart
await FirebaseAuth.instance.verifyPhoneNumber(
  codeSent: (String verificationId, int? resendToken) async {
    final smsCode = ''; 
// ダイアログでユーザーの認証コード入力を待つ
          await showDialog(
            context: context,
            builder: (_) {
              return AlertDialog(
                title: Text("認証コード"),
                content: Text("SMS宛に届いた認証コードを入力してください"),
                actions: <Widget>[
                  TextFormField(
                    onChanged: (value) {
                      smsCode = value;
                    },
                  ),
                  ElevatedButton(
                    child: Text("認証"),
                    onPressed: () => Navigator.pop(context),
                  ),
                ],
              );
            },
          );
// 認証コード(smsCode)とverificationIdをfirebaseに送信して認証
    final credential = PhoneAuthProvider.credential(verificationId: verificationId, smsCode: smsCode);
// 認証が完了したらFirebaseAuthにユーザー登録
    await auth.signInWithCredential(credential);
  },
);
```
- **codeAutoRetrievalTimeout**
タイムアウト時間を指定して、指定時間が過ぎたらこちらのハンドラが発火します。
```dart
await auth.verifyPhoneNumber(
  timeout: const Duration(seconds: 60),
  codeAutoRetrievalTimeout: (String verificationId) {
// デフォルトでは30秒で発火
  },
);
```

**全体コード**
iosのみの実装の場合は下記コピペで動くと思います。

```dart
  Future<void> verifyPhone(String phone) async {
    await FirebaseAuth.instance.verifyPhoneNumber(
      phoneNumber: '+81$phone',
      verificationCompleted: (PhoneAuthCredential credential) {
// iosの場合は空欄で問題なし
},
      verificationFailed: (FirebaseAuthException e) {
    if (e.code == 'invalid-phone-number') {
      print('電話番号が正しくありません。');
    }
},
      codeSent: (String verificationId, int? resendToken) {
    final smsCode = ''; 
          await showDialog(
            context: context,
            builder: (_) {
              return AlertDialog(
                title: Text("認証コード"),
                content: Text("SMS宛に届いた認証コードを入力してください"),
                actions: <Widget>[
                  TextFormField(
                    onChanged: (value) {
                      smsCode = value;
                    },
                  ),
                  ElevatedButton(
                    child: Text("認証"),
                    onPressed: () => Navigator.pop(context),
                  ),
                ],
              );
            },
          );
    final credential = PhoneAuthProvider.credential(verificationId: verificationId, smsCode: smsCode);
    await auth.signInWithCredential(credential);
},
      codeAutoRetrievalTimeout: (String verificationId) {
// タイムアウト時の挙動を指定しない場合は空欄でも問題なし
},
    );
  }
```

## 動作例

<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466417/17a47d56-3d1a-2c3e-f973-2726133d8cec.gif' width='250'>

電話番号認証はUXの向上や不正アカウントの防止などさまざまなメリットがありますし、firebaseを導入すると簡単に実装することができるので皆さんも是非実装してみてください。

## 参考
https://firebase.flutter.dev/docs/auth/phone/