📱

FlutterWebで電話番号認証やってみた

2023/05/07に公開

設定が変わっている?

FlutteWebで電話番号認証をやらなければならなくなったので、こちらの記事を参考にやってみました。
https://qiita.com/taisei_dev/items/7724e9faba369ec3979f

しかし、OAuthリダイレクトドメインの設定をしなくても自分のスマートフォンに、認証コードの通知が来ました?特別な設定は必要ないようです🤔

注意点

注意することは、タイムアウトを設定するコードは長い時間、120と長めに設定しておく必要があります。
認証コードを入力するところは番号を入力したら、エンターを押さないと画面遷移できません。

今回使用するパッケージはこちら

https://pub.dev/packages/firebase_core
https://pub.dev/packages/firebase_auth
認証コードを入力するサポートをしてくれるパッケージ
https://pub.dev/packages/pinput

早速使ってみた

無料枠と有料だと、使える回数に違いがあるみたいです。

設定をする
電話番号を選択する

有効にするにして、保存ボタンを押す

無料枠

有料プランにした場合

Flutterのコードを書く

海外の動画を参考にこちらのコードを作成いたしました。Androidで使われていましたが、Flutter Webで使ってみたところ問題なく動作していました。特殊な設定も必要ないみたいです。気になった点は、10回ぐらいやってたんですが、通知が来ないことがありました? 1日、3000回までできるらしいんですけどね?

ログイン後の画面

ログインに成功したら、こちらのページが表示されます。

home.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:phone_auth/login.dart';

class Home extends StatefulWidget {
  
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  String? uid;
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ログインに成功しました!'),
        actions: [
          IconButton(
            icon: const Icon(Icons.logout),
            onPressed: () async {
              await FirebaseAuth.instance.signOut();
              if(context.mounted) {
              Navigator.pushAndRemoveUntil(
                  context,
                  MaterialPageRoute(builder: (context) => LoginScreen()),
                  (route) => false);
              }
            },
          )
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Welcome!'),
            Text(uid!),
          ],
        ),
      ),
    );
  }

  
  void initState() {
    // TODO: implement initState
    super.initState();
    uid = FirebaseAuth.instance.currentUser!.uid;
  }
}

ログイン画面

こちらで、ご自身の電話番号を81(国際番号)以降の番号を入力してください。

login.dart
import 'package:flutter/material.dart';
import 'package:phone_auth/otp.dart';

class LoginScreen extends StatefulWidget {
  
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  TextEditingController _controller = TextEditingController();
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('電話番号認証APP'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Column(children: [
            Container(
              margin: const EdgeInsets.only(top: 60),
              child: const Center(
                child: Text(
                  '電話番号でログイン',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 28),
                ),
              ),
            ),
            Container(
              margin: const EdgeInsets.only(top: 40, right: 10, left: 10),
              child: TextField(
                decoration: const InputDecoration(
                  hintText: '81(日本の国番号)の後の番号を入力',
                  prefix: Padding(
                    padding: EdgeInsets.all(4),
                    child: Text('+1'),
                  ),
                ),
                maxLength: 10,
                keyboardType: TextInputType.number,
                controller: _controller,
              ),
            )
          ]),
          Container(
            margin: const EdgeInsets.all(10),
            width: double.infinity,
            child: ElevatedButton(
              onPressed: () {
                Navigator.of(context).push(MaterialPageRoute(
                    builder: (context) => OTPScreen(_controller.text)));
              },
              child: const Text(
                'Next',
                style: TextStyle(color: Colors.white),
              ),
            ),
          )
        ],
      ),
    );
  }
}

確認コードを入力するページ

こちらのページで、電話番号認証を実行します。

otp.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:phone_auth/home.dart';
import 'package:pinput/pinput.dart';

class OTPScreen extends StatefulWidget {
  final String phone;
  OTPScreen(this.phone);
  
  _OTPScreenState createState() => _OTPScreenState();
}

class _OTPScreenState extends State<OTPScreen> {
  final GlobalKey<ScaffoldState> _scaffoldkey = GlobalKey<ScaffoldState>();
  String? _verificationCode;
  final TextEditingController _pinPutController = TextEditingController();


  final defaultPinTheme = PinTheme(
      width: 56,
      height: 56,
      textStyle: const TextStyle(fontSize: 20, color: Color.fromRGBO(30, 60, 87, 1), fontWeight: FontWeight.w600),
      decoration: BoxDecoration(
        border: Border.all(color: Colors.black),
        borderRadius: BorderRadius.circular(20),
      ),
    );
  
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldkey,
      appBar: AppBar(
        title: const Text('確認コードを入力'),
      ),
      body: Column(
        children: [
          Container(
            margin: const EdgeInsets.only(top: 40),
            child: Center(
              child: Text(
                'Verify +1-${widget.phone}',
                style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 26),
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(30.0),
            child: Pinput(
              length: 6,
              defaultPinTheme: defaultPinTheme,

              controller: _pinPutController,

              pinAnimationType: PinAnimationType.fade,
              onSubmitted: (pin) async {
                try {
                  await FirebaseAuth.instance
                      .signInWithCredential(PhoneAuthProvider.credential(
                          verificationId: _verificationCode!, smsCode: pin))
                      .then((value) async {
                    if (value.user != null) {
                      Navigator.pushAndRemoveUntil(
                          context,
                          MaterialPageRoute(builder: (context) => Home()),
                          (route) => false);
                    }
                  });
                } catch (e) {
                  ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
                }
              },
            ),
          )
        ],
      ),
    );
  }

  _verifyPhone() async {
    await FirebaseAuth.instance.verifyPhoneNumber(
        phoneNumber: '+81${widget.phone}',
        verificationCompleted: (PhoneAuthCredential credential) async {
          await FirebaseAuth.instance
              .signInWithCredential(credential)
              .then((value) async {
            if (value.user != null) {
              Navigator.pushAndRemoveUntil(
                  context,
                  MaterialPageRoute(builder: (context) => Home()),
                  (route) => false);
            }
          });
        },
        verificationFailed: (FirebaseAuthException e) {
          print(e.message);
        },
        codeSent: (String? verficationID, int? resendToken) {
          setState(() {
            _verificationCode = verficationID;
          });
        },
        codeAutoRetrievalTimeout: (String verificationID) {
          setState(() {
            _verificationCode = verificationID;
          });
        },
        timeout: const Duration(seconds: 120));
  }

  
  void initState() {
    super.initState();
    _verifyPhone();
  }
}

アプリを実行する

main.dartのコードを書いて設定ができたら、firebase hostingをして、動作検証をしましょう。
こちらのサイトが参考になりました。
https://zenn.dev/snova301/books/6df29a230d681f/viewer/8d8b4a

main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:phone_auth/firebase_options.dart';
import 'package:phone_auth/login.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: LoginScreen(),
    );
  }
}

実行した結果

何度かやっていると、画像を選ばせて人間かロボットか調べるあの画面が出てくることがあります😅






まとめ

電話番号認証は使ったことがなかったので、新しい知見を得ることができました。皆さんも試してみてください。

Discussion