
Flutterで生体認証とセキュアストレージへの保存をしてみる
はじめに
こんにちは。リテールアプリ共創部のYahiroです。
生体認証ってありますよね。AppleならFaceID/TouchID、Androidなら指紋認証や顔認証などです。
アプリを使っていて、IDとパスワードをスマホに記憶させているケースは多いと思うのですが、生体認証→セキュアストレージからIDとパスワードを複合化までをFlutterで実装してみようと思います。
対象はAndroidとiOSです。
この記事でわかること
- FaceID、指紋認証の実装方法
- セキュア領域への保存方法
使用するパッケージ
- local_auth
- flutter_secure_storage
セキュアストレージ
今回、IDとパスワードはflutter_secure_storageというパッケージを利用して、セキュアストレージに保存します。
iOSではKeyChainに保存されます。
Androidでは、AES暗号が使用されます。AES暗号秘密鍵はRSAで暗号化され、RSA鍵はKeyStoreに保存されます。
事前設定
AndroidManifest、Info.Plistにそれぞれ以下を設定してください。
AndroidManifest
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
Info.Plist
<key>NSFaceIDUsageDescription</key>
<string>ログイン情報を取得するためにFace IDを使用します</string>
AndroidのMainActivity.ktを以下に変更してください。これを行うことにより、FlutterFragmentActivityが利用可能になり、生体認証が動作します。
MainActivity.kt
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity : FlutterFragmentActivity()
実装の流れ
冒頭で記載のとおり、生体認証、保存した認証情報の取得の流れで実装していきます。生体認証は認証結果がOKかNGかの二択のみを提供するため、まず、生体認証を検証し、OKならセキュアストレージからIDとパスワードを複合化してログインを行うという流れにすると良いと思います。
生体認証
- 生体認証が利用可能かチェック
まずは、その端末で生体認証が可能かの検証を行います。
static final LocalAuthentication _localAuth = LocalAuthentication();
static Future<bool> isBiometricAvailable() async {
try {
final bool canAuthenticateWithBiometrics =
await _localAuth.canCheckBiometrics;
final bool canAuthenticate =
canAuthenticateWithBiometrics || await _localAuth.isDeviceSupported();
return canAuthenticate;
} catch (e) {
return false;
}
}
- 利用可能な認証タイプの取得
端末がで利用可能な認証タイプを配列で取得できます。
// 利用可能な生体認証タイプを取得
static Future<List<BiometricType>> getAvailableBiometrics() async {
try {
final biometrics = await _localAuth.getAvailableBiometrics();
return biometrics;
} catch (e) {
return [];
}
}
- 生体認証の実行
実際に生体認証を実行してみましょう。localizedReasonには認証時にユーザに表示するメッセージです。空にできないので、適宜設定してください。
biometricOnlyをtrueにした場合、生体認証のみを許可します。trueの場合、パスワード、PINなどでの認証は無効になります。デフォルトはfalseです。
stickyAuthをtrueにした場合、アプリがバックグラウンドへ切り替わったりした場合でも、認証を続行します。デフォルトはfalseです。
生体認証に成功するとtrueが返却され、失敗するとfalseが返却されます。
// 生体認証を実行
static Future<bool> authenticateWithBiometrics() async {
try {
final result = await _localAuth.authenticate(
localizedReason: 'ログイン情報を取得するために生体認証を行ってください',
options: const AuthenticationOptions(
biometricOnly: true,
stickyAuth: true,
),
);
return result;
} catch (e) {
return false;
}
}
セキュアストレージへの保存と取得
- 認証情報の確認・保存・取得
続いて、認証情報の確認・保存・取得です。
確認では、Keyに対応する値が存在するかを確認します。存在しなければfalseで返されます。
保存・取得ではそれぞれ、Keyに対して値の保存、Keyに対応する値の取得を行います。
// Keyは任意に設定してください
static const String _savedIdKey = 'saved_user_id';
static const String _savedPasswordKey = 'saved_user_password';
// 生体認証が有効かチェック
static Future<bool> isBiometricEnabled() async {
try {
final credentials = await getSavedCredentials();
return (credentials['id']?.isNotEmpty ?? false) &&
(credentials['password']?.isNotEmpty ?? false);
} catch (e) {
return false;
}
}
// 認証情報を保存
static Future<void> saveCredentials(String id, String password) async {
try {
await _secureStorage.write(key: _savedIdKey, value: id);
await _secureStorage.write(key: _savedPasswordKey, value: password);
} catch (e) {
// エラーハンドリング
}
}
// 保存された認証情報を取得
static Future<Map<String, String?>> getSavedCredentials() async {
try {
final id = await _secureStorage.read(key: _savedIdKey);
final password = await _secureStorage.read(key: _savedPasswordKey);
return {'id': id, 'password': password};
} catch (e) {
return {'id': null, 'password': null};
}
}
取得するとMap形式でStringでID/パスワードが返却されるため、これを使用してテキストフィールドへの反映や、ログインを行なってください。
- 保存した認証情報の削除
セキュアストレージに保存した認証情報を削除したい場合、以下で削除ができます。
static const String _savedIdKey = 'saved_user_id';
static const String _savedPasswordKey = 'saved_user_password';
// 保存された認証情報を削除
static Future<void> clearSavedCredentials() async {
try {
await _secureStorage.delete(key: _savedIdKey);
await _secureStorage.delete(key: _savedPasswordKey);
} catch (e) {
// エラーハンドリング
}
}
最後に
Flutterでは、生体認証のパッケージが存在するため実装が簡単できます。今回はスマホに限定して検証を行っていますが、Flutterのクロスプラットフォームな特性を活かして、他のプラットフォームでも生体認証の実装が可能なので色々応用ができそうです。