【Flutter】アプリ起動時に生体認証をする
はじめに
私がよく見かける(と思ってる)、アプリを開くと同時に生体認証を求められる機能を実装しようと思います。
とは言っても、認証機能自体はほとんどドキュメントコピペでよくて、どうナビゲーションするかに少し悩みました。
実装
パッケージはlocal_authとpermisiion_handlerを使います。
準備
iOSならInfo.plist
にNSFaceIDUsageDescriptionを追加。
<key>NSFaceIDUsageDescription</key>
<string>使わせてね</string>
AndroidならAndroidManifest.xml
にUSE_BIOMETRICを追加し、
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
MainActivity.kt
の中身を変更します。
- import io.flutter.embedding.android.FlutterActivity
+ import io.flutter.embedding.android.FlutterFragmentActivity
- class MainActivity: FlutterActivity() {}
+ class MainActivity: FlutterFragmentActivity() {}
コード
まず認証を行うページを作ります。
こんな感じです。
ドキュメントからコピペしてちょっといじっただけです。
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:local_auth/local_auth.dart';
import 'package:permission_handler/permission_handler.dart';
class LocalAuthPage extends StatefulWidget {
const LocalAuthPage({super.key});
State<LocalAuthPage> createState() => _LocalAuthPageState();
}
class _LocalAuthPageState extends State<LocalAuthPage> {
final LocalAuthentication auth = LocalAuthentication();
bool _canCheckBiometrics = false;
Future<void> _checkBiometrics() async {
late bool canCheckBiometrics;
try {
canCheckBiometrics = await auth.canCheckBiometrics;
} on PlatformException catch (e) {
canCheckBiometrics = false;
debugPrint(e.toString());
}
if (!mounted) {
return;
}
setState(() {
_canCheckBiometrics = canCheckBiometrics;
});
}
Future<void> _authenticate() async {
bool authenticated = false;
try {
authenticated = await auth.authenticate(
localizedReason: '生体認証するよ',
options: const AuthenticationOptions(
stickyAuth: true,
),
);
} on PlatformException catch (e) {
debugPrint(e.toString());
return;
}
if (!mounted) {
return;
}
if (authenticated) {
backToPreviousPage();
}
}
void backToPreviousPage() {
Navigator.pop(context);
}
void initState() {
super.initState();
_authenticate();
_checkBiometrics();
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('生体認証してね'),
ElevatedButton(
onPressed: () => _authenticate(), child: const Text("する")),
if (!_canCheckBiometrics)
ElevatedButton(
onPressed: () => openAppSettings(),
child: const Text('オンにする'))
],
),
),
);
}
}
以下の部分で、認証が成功した際に前のページに戻るようにしています。
if (authenticated) {
backToPreviousPage();
}
次に、生体認証ページに飛ぶ前のページです。
親の顔より見たカウンターアプリにナビゲーションを追加しただけです。
import 'package:flutter/material.dart';
import 'local_auth_page.dart';
class CounterPage extends StatefulWidget {
const CounterPage({super.key, required this.title});
final String title;
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
void initState() {
super.initState();
Future<void>(() => Navigator.push(
context, MaterialPageRoute(builder: (_) => const LocalAuthPage())));
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
initState()
内でNavigator.push()
はできないので、build()
後に実行されるようにしていますが、このやり方が正解なのかは分かりません。
Future<void>(() => Navigator.push(
context, MaterialPageRoute(builder: (_) => const LocalAuthPage())));
iOSのviewDidAppear()
みたいなのがあればそこで実行したかったのですが、Flutterにはこれに値するものがなさそうです。
動作
iOS
こんな感じで動きます。
iOSのシミュレータでFace IDを使うには、Features -> Face ID -> Enrolled
でオンにして、認証時にMatching Face
で認証成功、Non-matching Face
で認証失敗できます。
Touch IDでも同様です。
Android
同様に動きます。
Androidエミュレータは、設定のSecurity & Privacy -> Device unlock
で指紋を登録できます。
登録する指紋は、エミュレータの上にある3点リーダーを押して出てくる画面で選べます。
Touch Sensor
を押すと、選択している指紋で画面をタップしてくれます。
指紋認証をする時もこの画面で指紋を使います。
終わりに
公式がパッケージを用意してくれているおかげで、楽でいいですね。
Discussion